Modify Objects In-Place
September 22, 2021 in ruby

Every time you create or copy something in memory, you add work for GC. Here are some tips from the book Ruby Performance Optimization to write code without using too much memory.

Modify strings in-place.

Instead of making a changed copy, you change the original string. Look for “bang!” functions, such as trim!, gsub!, slice! etc. Use them when you don’t need the original string.

require_relative "./wrapper" # see above for wrapper script

def modify_strings_in_place
  str = "X" * 1024 * 1024 * 10
  measure("copy") { tmp = str.downcase }
  measure("in-place") { str.downcase! }
end

modify_strings_in_place

# Output
# {"3.0.2":{"gc":"disabled","time":0.01,"gc_count":0,"memory":"10 MB"}}
# {"3.0.2":{"gc":"disabled","time":0.01,"gc_count":0,"memory":"0 MB"}}

Use the shift operator << to append strings, instead of using +=.

When you use +=, Ruby creates an intermediate string and assigns it to the original string after appending.

def append_to_string
  measure do
    x = "foo"
    100_000.times { x += "bar" }
  end

  measure do
    x = "foo"
    100_000.times { x << "bar" }
  end
end

append_to_string

# Output
# {"3.0.2":{"gc":"disabled","time":9.42,"gc_count":0,"memory":"6412 MB"}}
# {"3.0.2":{"gc":"disabled","time":0.01,"gc_count":0,"memory":"-2 MB"}}

Modify Arrays in-place.

Do not create a modified copy of the same array unless really necessary.

def modify_array_in_place
  data = Array.new(100) { "x" * 1024 * 1024 }

  measure { data.map { |str| str.upcase } }
  measure { data.map { |str| str.upcase! } }
end

modify_array_in_place

# Output
# {"3.0.2":{"gc":"disabled","time":0.12,"gc_count":0,"memory":"100 MB"}}
# {"3.0.2":{"gc":"disabled","time":0.1,"gc_count":0,"memory":"0 MB"}}

Hope that helps.