RubyGems memory reduction... 3

Posted by Steve Longdo Sat, 12 Aug 2006 07:00:00 GMT

I did gain some insight into the number of Gem::GemPathSearcher instances through my efforts. They correspond nicely for each piece of Rails that is loaded; ActionMailer, ActiveSupport, ActiveRecord, ActionPack, ActionWebService and Railties. Each of these has the require 'rubygems' statement in it. I believe this is why I was seeing multiple instances of Gem::GemPathSearcher.

As explained before instances of these take up an amount of memory proportional to the number of gems/gem versions in a repository. Making Gem::GemPathSearcher a Singleton of course, resolves more than one instance being loaded into memory. This has been submitted as a patch to RubyGems [ticket #5394].

Installing a patched version of RubyGems 0.9.0 on TextDrive to demonstrate the fix with Typo proved to be the most frustrating part. I will write a follow up posting on exactly how to do this in the near future. Hopefully they will release an updated RubyGems soon!

Shared hosts will definitely want to patch their RubyGems installs to reduce their memory consumption across all of their Rails users. TextDrive in particular should buy me a present or something :-)

I've learned a great deal about the Ruby memory ecosystem by digging into this issue with RubyGems. I got help from some folks on the Rails mailing list but far and away I want to thank Stef Telford for the help. Mad props to you for putting up with all of my questions and ramblings!

Finally some numbers:
Gem::Dependency x330(57415)
Gem::Version::Requirement x693(61939)
Gem::SourceIndex x1(1632488)
Gem::GemPathSearcher2 x1(1647806) <-- Gem::GemPathSearcher x5(7901012)
Hash x1219(1789928)
Gem::Specification x362(1795089)
String x113992(3876455)
Array x7415(5080185)
TOTAL == 16967273                          <-- TOTAL == 28496875

Update: These memory numbers are lower than actual process size. They vary by number of Modules loaded as well as Objects counts. Not all Objects and Modules can be evaluated for memory size. The new routing code in the latest version of Rails may also account for some of the difference as well. So grain of salt...

RubyGems: Single/Multi-tons and memorysize 2

Posted by Steve Longdo Wed, 09 Aug 2006 03:41:00 GMT

There is a line in the RubyGems source code, @gempath_searcher ||= Gem::GemPathSearcher.new, from custom_require.rb that seeks to make Gem::GemPathSearcher a Singleton class. For some reason this isn't sufficient as I have seen as many as five instances of it existing in the same thread during profiling of Typo. I am not sure why more than one gets generated. It may relate to Rails aliasing the Kernel.require method after RubyGems has in custom_require.rb.

Review of the source makes me understand why a Gem::GemPathSearcher instance can be so large, it holds the gem specifications for every gem and version on the load path. Running locally this is not a big deal, as you probably don't have a lot of gems and their versions installed. On a shared host though there are tons of gems and versions of gems installed (350+ on my TextDrive server!).

A permanent solution to fix Gem::GemPathSearcher to truly be a Singleton class would probably be something as simple as checking Object.const_defined?(:GemPathSearcher) to see if the class is already in memory.

A quick alternative would be restricting the gem library path to a gem repository other than the shared host's repository. This can be done by exporting the GEM_HOME variable in your shell. There is also a GEM_PATH variable that can be used to support multiple repositories. Examples of how to work with these variables can be found on the Rails wiki. It should be possible to make a repository containing just the gems your app needs and nothing else. This will reduce the memory footprint of Gem::GemPathSearcher quite a bit, no matter how many instances of them there might be!

Obviously I am still learning the implications of RubyGems working together with Rails apps. I apologize for you the reader being dragged along on my quest to learn. Anyone wishing to bring this quest to a quick end just tell me how it ends.

Update: I will continue my research until we arrive at the truth. I still have some other RubyGems optimizing techniques to apply. My hope is using something like the gemconfigure file described in the documentation will be the difference between the Whole Milk Memory and Skim Milk Memory. Also I will reformat the code in the post to be pretty tomorrow. need to add some stuff to scribbish to accomplish this and I want to go to bed now...