How Ruby Uses Memory

Originally published at: http://www.sitepoint.com/ruby-uses-memory/
ram 2

I’ve never met a developer who complained about code getting faster or taking up less RAM. In Ruby, memory is especially important, yet few developers know the ins-and-outs of why their memory use goes up or down as their code executes. This article will start you off with a basic understanding of how Ruby objects relate to memory use, and we’ll cover a few common tricks to speed up your code while using less memory.

Object Retention

The most obvious way that Ruby grows in memory usage is by retaining objects. Constants in Ruby are never garbage collected so if a constant has a reference to an object, then that object can never be garbage collected.

RETAINED = []
100_000.times do
  RETAINED < < "a string"
end

If we run this and debug with GC.stat(:total_freed_objects) it will return the number of objects that have been released by Ruby. Running this snippet before and after results in very little change:

# Ruby 2.2.2

GC.start
before = GC.stat(:total_freed_objects)

RETAINED = []
100_000.times do
  RETAINED < < "a string"
end

GC.start
after = GC.stat(:total_freed_objects)
puts "Objects Freed: #{after - before}"

# => "Objects Freed: 6

We have created 100,000 copies of "a string" but since we might use those values in the future, they can’t be garbage collected. Objects cannot be garbage collected when they are referenced by a global object. This goes for constants, global variables, modules, and classes. It’s important to be careful referencing objects from anything that is globally accessible.

If we do the same thing without retaining any objects:

100_000.times do
  foo = "a string"
end

The objects freed skyrockets: Objects Freed: 100005. You can also verify that the memory is much smaller, around 6mb compared to the 12mb when retaining a reference to the objects. Measure it yourself with the get_process_mem gem, if you like.

Object retention can be further verified using GC.stat(:total_allocated_objects), where retention is equal to total_allocated_objects - total_freed_objects.

Continue reading this article on SitePoint

Great write-up!

I didn’t know about Regex objects, but come to think of it, it makes sense for the VM to “freeze” them since they are immutable anyway.

I guess memory would be released faster back to the OS in Ruby 1.9 since the collector is neither generational nor incremental (as it is in Ruby 2.2).

I love articles like this, especially when it’s about Ruby. Awesome stuff.

While everything about constants is important, nobody would on purpose create a constant containing a dozillion strings. There is another pitfall with ruby memory management, though. It addresses to RVALUEs and how ruby internally allocates memory in internal heaps.
In general, whether one creates an object that requires less than 23 bytes, it’s being stored in internal heap. These heaps are never returned to OS during a process run.
More details: http://rocket-science.ru/hacking/2013/12/17/ruby-memory-pitfalls/

Nice content. (:

I’d like to nitpick that User.where(name: "schneems").first can be slow because it searches through the whole table and we care only about first result. User.where(name: "schneems").limit(1) is better

If you look at the SQL it generates, they both generate a limit:

irb(main):001:0>  User.where(name: "schneems").first
  User Load (7.9ms)  SELECT  "users".* FROM "users" WHERE "users"."name" = $1  ORDER BY "users"."id" ASC LIMIT 1  [["name", "schneems"]]

versus

irb(main):002:0>  User.where(name: "schneems").limit(1)
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."name" = $1 LIMIT 1  [["name", "schneems"]]

The first method is understood by the Active Record relation to be a limiting factor

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.