I just added the following config to a ruby test suite:
RSpec.configure do |config| config.before :each do GC.disable end config.after :each do GC.enable end end |
It’s pretty tragic how well this works. It knocked about 40% off the runtime of the test suite for our Padrino + DataMapper app running on 1.8.7 (yes, I know. I’m sorry. I actually discovered this as part of my work on getting it off 1.8.7), and a friend is reporting a more than 50% drop for his Rails app running on 1.9.3.
Why? Well, I added this as an experiment when I noticed how much time the test suite was spending in the GC (I initially ran with GC turned off entirely. This ate about as much memory as you’d expect). I’m assuming it’s a combination of MRI’s GC being really quite shit and the fact that test setup tends to produce a lot of garbage.
This is kinda an awful solution to this problem, but I must admit I’m finding it hard to argue with the results. If you can afford to throw memory at the problem it might actually be worth doing.
But still… ouch.
Did you try with Ruby 2.0? If not, can you and share the results?
No, I can’t unfortunately. This app is actually stuck on 1.8.7 and I discovered this hack as part of the attempt to upgrade it to a more recent version, but the rails app with the 50% drop is on 1.9.3.
It’s entirely possible this problem goes away in 2.0, but I’ve no way to verify it.
Can this hack still be used and helps to this day?
I can report 0% drop on our Rails app running 1.9.3p374 :-(
Huh. Well, good to know it doesn’t work for everything. And somewhat reassuring. Sorry you didn’t get the magic speedup I promised though!
I get similar results by tuning some GC parameters in my bashrc. This results in significantly fewer GC runs (and the associated memory bloat) but feels a little safer than turning GC off entirely.
export RUBY_HEAP_MIN_SLOTS=1000000
export RUBY_HEAP_SLOTS_INCREMENT=1000000
export RUBY_HEAP_SLOTS_GROWTH_FACTOR=1
export RUBY_GC_MALLOC_LIMIT=100000000
export RUBY_HEAP_FREE_MIN=500000
running with GC disabled my suite runtime is 38.5 s compared to 39.9 with the above settings. Some more good information is here: http://stackoverflow.com/questions/4985310/garbage-collector-tuning-in-ruby-1-9
This isn’t for everything, but in certain apps that thrash memory during tests I sometimes do a similar, but more drastic, setup. On test start I set a variable with the current time. After leaving the spec/feature, if ~2 seconds have passed, I clear the GC and update the variable with the current time. It eats way more RAM than your approach and invokes the GC a lot less often, but since it’s the last step after optimizing/tuning/cleaning up the rest of the app/test suite, if it happens to shave some time off of the tests, and only takes 10 lines of code to invoke, I’ll do it.
Yep, can confirm that 1.9.3 does not really benefit from this. Yay?
With GC disabled (in the rspec’s before each):
Finished in 10.73 seconds
Normal GC:
Finished in 10.73 seconds
My ruby version:
ruby --version
ruby 1.9.3p392
This is a good tip particularly to those new to the blogosphere.
Simple but very precise information… Appreciate your sharing this one.
A must read article!