When Rails 3 was released, many users noticed that Ruby 1.9.2 and Ruby-head (basically 1.9.3) seemed to be awfully slower when loading Rails 3 apps, than Ruby 1.8.7. Then, back in May, Xavier Shay posted an article to his blog with some interesting findings: he noticed that the way later versions of Ruby require files and keep track of the files that have already been loaded isn’t very efficient, causing Rails 3 apps (which require thousands files at startup) to load a lot more slowly. He also released an awesome patch for Ruby 1.9.3, that did seem to improve the loading times and make them more comparable to those of Ruby 1.8.7, so other patches were then released for Ruby 1.9.2 as well (I suggest you read his post for more details; you can also find more information if you Google for ‘Ruby 1.9 fast require patch’).
Over the past few months, I have been sticking to a patched version of Ruby 1.9.2-p180, without upgrading to the latest stable revision (p290) since I couldn’t find yet a version of the ‘fast require’ patch that would work correctly with this revision as well. Yesterday, however, I started working a new Rails 3.1 app, and it seemed that the p180 revision wasn’t working well with the latest release of Rails. I am not sure of whether this had something to do with the particular configuration of my system (I tried reinstalling RVM and Ruby a few times though), but I would get warning and errors mainly related to Sass like
...(cut)... ... warning: already initialized constant ROOT_DIR ... warning: already initialized constant RUBY_VERSION ... warning: already initialized constant RUBY_ENGINE ... warning: already initialized constant ENCODINGS_TO_CHECK ... warning: already initialized constant CHARSET_REGEXPS ... warning: already initialized constant PARENT ...(cut)...
... script/rails:6:in `require' script/rails:6:in `<main>' error scss [not found]
when, for example, bootstrapping the administration of some model with a quick and dirty scaffolding.
I didn’t want to spend much time investigating this, so also out of curiosity I tried to install an updated version of Ruby to see if it would help – and in fact it did, so I was lucky. After trying a few patches for the ‘fast require’, I eventually found one that actually worked with p290, so here’s how you’d use it to install and patch the latest stable revision of Ruby 1.9.2, with RVM:
curl https://raw.github.com/gist/1008945/4edd1e1dcc1f0db52d4816843a9d1e6b60661122/ruby-1.9.2p290.patch > /tmp/192.patch rvm uninstall 1.9.2 && rvm cleanup all && rvm fetch 1.9.2 rvm install 1.9.2 --patch /tmp/192.patch
At first, I tried to install the relevant packages with RVM:
rvm pkg install iconv rvm pkg install readline rvm uninstall 1.9.2 && rvm cleanup all && rvm fetch 1.9.2 rvm install 1.9.2 --patch /tmp/192.patch --with-readline-dir=$rvm_usr_path --with-iconv-dir=$rvm_usr_path
but this would blow up when also applying the ‘fast require’ patch:
... ruby-1.9.2-p290 - #fetching ruby-1.9.2-p290 - #extracted to /Users/vito/.rvm/src/ruby-1.9.2-p290 (already extracted) Applying patch '/tmp/192.patch' (located at //tmp/192.patch) ERROR: Error running 'patch -F25 -p1 -f <"//tmp/192.patch"', please read /Users/vito/.rvm/log/ruby-1.9.2-p290/patch.apply.192.patch.log ... ERROR: There has been an error while running configure. Halting the installation.
I believe this was due to the fact that RVM also needs to apply a second patch to Ruby, in order to install readline, if readline is being installed with RVM. I am not sure of how to apply more than one patch at the same time (unless I am missing something, I don’t think this is possible?); I also got weird errors related to iconv (for example required by the json gem), when trying to start an app or to execute a rake task:
/Users/vito/.rvm/gems/ree-1.8.7-2011.03/gems/json-1.5.1/lib/json/common.rb:98: warning: already initialized constant NaN /Users/vito/.rvm/gems/ree-1.8.7-2011.03/gems/json-1.5.1/lib/json/common.rb:100: warning: already initialized constant Infinity /Users/vito/.rvm/gems/ree-1.8.7-2011.03/gems/json-1.5.1/lib/json/common.rb:102: warning: already initialized constant MinusInfinity /Users/vito/.rvm/gems/ree-1.8.7-2011.03/gems/json-1.5.1/lib/json/common.rb:121: warning: already initialized constant UnparserError rake aborted! no such file to load -- iconv /Users/vito/.rvm/gems/ree-1.8.7-2011.03/gems/json-1.5.1/lib/json/common.rb:358:in `require' /Users/vito/.rvm/gems/ree-1.8.7-2011.03/gems/json-1.5.1/lib/json/common.rb:358 /Users/vito/.rvm/gems/ree-1.8.7-2011.03/gems/json-1.5.1/lib/json.rb:1:in `require' /Users/vito/.rvm/gems/ree-1.8.7-2011.03/gems/json-1.5.1/lib/json.rb:1 ...(cut)...
Ruby 1.9.x already includes some equivalent of the iconv gem by default, so I am not sure of what triggered this error. I even tried to install the iconv gem anyway, without much success (‘failed to build native extensions’ for some reason).
I found a workaround by installing both readline and iconv with Homebrew instead:
brew install readline brew install libiconv brew link libiconv rvm cleanup all && rvm fetch 1.9.2 rvm install 1.9.2 --patch /tmp/192.patch --with-readline-dir=/usr/local/Cellar/readline/6.2.1 --with-iconv-dir=/usr/local/Cellar/libiconv/1.14
Note that I also had to run brew link libiconv due to some other error. This is now working as expected, and I can definitely see the speedier startup with my Rails 3 apps, while the aforementioned dependencies also work. If you install the patch, it’s simple to compare the startup time before and after the patch. Here’s an example with one application I’m working on:
time rails runner 'a=1' real 0m21.427s user 0m17.585s sys 0m2.004s
time rails runner 'a=1' real 0m14.725s user 0m12.721s sys 0m1.996s
As you can see, in this case the patch cut several seconds from the startup time, although it also depends a lot on the application and on how large it is.
The shorter startup time can make a nice difference especially when you are running tests (unless you’re using something like spork, that is), so if you haven’t patched your Ruby install yet, for whatever reason, I’d definitely recommend you to.