Making Ruby’s garbage collector copy-on-write friendly, part 6 (final?)
I added some minor optimizations and cleaned up the code a little bit. I also fixed many bugs in mongrel_light_cluster. It’s time for another test round.
Test plan
The goal is to measure the memory usage of my Ruby GC patch + mongrel_light_cluster, compared to default Ruby and mongrel_cluster. The test plan is as follows:
- Measure current memory usage.
- Start 10 instances of mongrel_cluster.
- Measure current memory usage.
- Load the front page of all 10 mongrel_cluster instances 50 times.
- Measure current memory usage.
- Shutdown mongrel_cluster, and repeat the above steps with patched Ruby + mongrel_light_cluster.
Software used in this test
Ruby on Rails 1.2.5
Mongrel 1.0.1
mongrel_cluster 1.0.2
Mephisto 0.7.3
Environment: production
config.action_controller.perform_caching = false
Memory usage of 10 mongrel_cluster instances
Before mongrel_cluster is started:
total used free shared buffers cached Mem: 1011 576 435 0 16 185 -/+ buffers/cache: 373 637 Swap: 996 13 982
After starting 10 instances of mongrel_light_cluster:
total used free shared buffers cached Mem: 1011 861 149 0 16 185 -/+ buffers/cache: 659 352 Swap: 996 13 982
After downloading the front page 50 times from all instances:
total used free shared buffers cached Mem: 1011 901 109 0 16 186 -/+ buffers/cache: 698 312 Swap: 996 13 982
Conclusion:
10 instances of mongrel_cluster will use 435-109 = 326 MB memory.
So mongrel_cluster uses 32.6 MB per instance.
Memory usage of 10 mongrel_light_cluster instances with patched Ruby
Before:
total used free shared buffers cached Mem: 1011 587 424 0 18 193 -/+ buffers/cache: 375 635 Swap: 996 13 982
After starting 10 instances:
total used free shared buffers cached Mem: 1011 752 259 0 18 193 -/+ buffers/cache: 540 470 Swap: 996 13 982
After downloading the front page 50 times from all instances:
total used free shared buffers cached Mem: 1011 803 207 0 18 193 -/+ buffers/cache: 591 419 Swap: 996 13 982
Conclusion:
10 instances of mongrel_light_cluster uses 424-207 = 217 MB memory.
So 1 mongrel_light_cluster instance uses 21.7 MB.
Overall conclusion
Everybody likes graphs, so here is one:

Memory usage of an instance went down from 32.6 MB to 21.7 MB. A 33% saving!
This seems to be the end of the road. I can’t seem to be able to add more optimizations.
You can download the latest Ruby 1.8.6 patch here.
You can install mongrel_light_cluster by typing:
svn checkout http://public.railsplugins.net/repos/mongrel_light_cluster/trunk mongrel_light_cluster cd mongrel_light_cluster rake gem install pkg/mongrel_light_cluster-0.1.gem
Please read the README for further documentation.
Finally, I’d like to ask your help:
- I want to turn this patch and mongrel_light_cluster into a real RubyForge project. Can anyone help me with the website and documentation?
- Is there anyone willing to test my patch and mongrel_light_cluster? I’m not confident enough to use these on my business website (which handles payments) but I don’t have any other Rails sites that are large enough.
Please post here if you’re interested.

Pratik said,
October 15, 2007 @ 3:01 pm
Just wondering if your patch would work fine with LiteSpeed ? In which case I could probably try it on my one of my server. I’d love to help you out with my limited C knowledge and decent ruby knowledge. Also, why do you have config.action_controller.perform_caching set to false ? Well, I keep forgetting your irc nick, mine is “lifo”, so do catch me in there
Thanks.
Hongli said,
October 15, 2007 @ 4:37 pm
Hey.
I will work fine with LiteSpeed. The patch is only for making Ruby GC copy-on-write friendly. To properly integrate this with LiteSpeed I have to figure out how LiteSpeed runs Rails. If it’s just FastCGI then the solution should be pretty straightforward. Do you have a website running on LiteSpeed? I only have experience with Mongrel and lighttpd.
I set perform_caching to false so that each request actually triggers the controller, in order to test the garbage collector. If perform_caching is set to true, then Mephisto will generate .html files which Mongrel will serve directly, thus bypassing Rails.
Jeremy said,
October 15, 2007 @ 4:45 pm
I am testing this now with two Rails apps, and swiftiply_mongrel_rails. Startup is 17 megs of RAM, fully loaded is 21-22 megs.
Hongli said,
October 15, 2007 @ 5:27 pm
I’m not sure whether mongrel_light_cluster is compatible with swiftiply_mongrel_rails. You can check whether mongrel_light_cluster is actually active by checking this:
module Cluster module ExecBase if @@light_cluster_initialized puts "mongrel_light_cluster is active" end end endJeremy said,
October 15, 2007 @ 5:51 pm
Already did that.
- It is.
Jeremy said,
October 15, 2007 @ 6:03 pm
Actually, upon further review, it doesn’t. They don’t call start. I had a check inside the module, which was outputting, but start never gets called. I’ll just run with the standard mongrel for now, and change up “swiftiply_mongrel_rails” to use your stuff instead later.
Roger Pack said,
October 22, 2007 @ 10:11 pm
You should submit this to the mongrel guys
lloyd hilaiel said,
October 26, 2007 @ 9:30 pm
hi.
I’m embarked on a similar mission. make ruby use less memory without going too much slower. I’ve got a set of very simple test cases, and ran then on 1.8.6 after applying your patch. it’s interesting to note that you’ve got about a 8% performance hit in combination with the 33% memory usage gain according to my toy tests:
vanilla 1.8.6:
running cases/growarray.rb
time 1.355438
running cases/plist.rb
time 3.693722
running cases/shrinkarray.rb
time 1.351735
cowfriendly 1.8.6:
running cases/growarray.rb
time 1.485687
running cases/plist.rb
time 4.006008
running cases/shrinkarray.rb
time 1.48557
(/ 3.693722 4.006008)
(/ 1.351735 1.48557)
~8% performance penalty.
I’d imagine for a lot of folks this is a tradeoff they’re willing to make (8% is a lot better than swapping and paging :), but thought it might be interesting to you anyways.
赖洪礼的 blog » Making Ruby’s garbage collector copy-on-write friendly, part 7 said,
January 14, 2008 @ 1:50 pm
[…] turns out that part 6 was not final. I’ve been working on the garbage collector again. These are the […]
roger said,
March 20, 2008 @ 6:01 am
I assume most of the memory-savings is because of using fixed-length “heap blocks”?
Hongli said,
March 20, 2008 @ 11:07 am
No, the memory savings are caused by copy-on-write semantics.
Crystal said,
April 17, 2008 @ 1:35 am
Wow these are very big savings, thanks again.