Ruby does automatic Garbage Collection.


Tuning the GC

edit

MRI's GC is a "full mark and sweep" and is run whenever it runs out of memory slots (i.e. before adding more memory, it will sweep the existing to see if it can free up some first--if not it adds more memory). It also is triggered after GC_MALLOC_LIMIT of bytes has been allocated by extensions. Unfortunately this causes a traversal of all memory, which is typically slow. See a good description.

The GC is known to typically take 10% of cpu, but if you have a large RAM load, it could take much more.

GC can be tuned at "compile time" (MRI/KRI's < 1.9) http://blog.evanweaver.com/articles/2009/04/09/ruby-gc-tuning or can be tuned by using environment variables (REE, MRI/KRI's >= 1.9).

Some tips:

You can set the compiler variable GC_MALLOC_LIMIT to be a very high value, which causes your program to use more RAM but to traverse it much less frequently. Good for large apps, like rails.

You can use jruby/rubinius which use more sophisticated GC's.

You can use "native" libraries which store the values away so that Ruby doesn't have to keep track of them and collect them. Examples: "NArray" gem and "google_hash" gem.

To turn it off: @GC.disable@

To force it to run once: @GC.start@

Conservative

edit

Ruby's (MRI's) GC is mark and sweep, which means it is conservative. To accomplish this, it traverses the stack, looking for any section of memory which "looks" like a reference to an existing ruby object, and marks them as live. This can lead to false positives, even when there are no references to an object remaining.

This problem is especially bad in the 1.8.x series, when they don't have the MBARI patches applied (most don't, REE does). This is because, when you use threads, it actually allocates a full copy of the stack to each thread, and as the threads are run, their stack is copied to the "real" stack, and they can pick up ghost references that belong to other threads, and also because the 1.8 MRI interpreter contains huge switch statements, which leave a lot of memory on the stack untouched, so it can continue to contain references to "ghost" references in error.

This all means that if you call a GC.start, it's not *guaranteed* to collect anything.

Some hints around this:

  • If you call your code from within a method, and come *out* of that method, it might collect it more readily.
  • You can do something of your own GC by using an ensure block, like
 a = SomeClass.new
begin
 ...
ensure
 a.cleanup
end
  • If you write a "whole lot more" memory it might clear the stack of its old references.

Tunning Jruby's GC.

edit

"here":http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/27550 is an example of how to tune Jruby's GC. The G1GC is theoretically a "never pause" GC, though in reality most of them are pretty good at being speedy. For long running apps you'll probably want to run in server mode (--server), for increased performance, though decreased startup time.

Rubinius is also said to have a better GC.

How to avoid performance penalty

edit

Since MRI's GC is basically O(N) as it grows, you get a performance penalty when GC's occur and you are using a lot of RAM in your app (and MRI almost never gives its RAM back to the system). Workarounds:

  1. use less RAM by allocating fewer objects
  2. do work in a forked "child process" which returns back the values desired. The child will die, freeing up its memory.
  3. use Jruby et al (jruby has an excellent GC which doesn't slow things down much, even with larger apps).
  4. use a gem that allows for native types, like NArray or the RubyGoogle Hash.
  5. use REE instead of 1.8.6 (since it incorporates the MBARI patches which make the GC much more efficient).
  6. use 1.9.x instead of 1.8.6 (since it uses true threads there is much less reference ghosting on the stack, thus making the GC more efficient).
  7. set your app to restart periodically (passenger can do this).
  8. create multiple apps, one designed to be large and slow, the others nimble (run your GC intensive all in the large one).
  9. call GC.start yourself, or mix it with GC.disable
  10. use memprof gem to see where the leaks are occurring (or the dike gem or the like).