Sunday, March 14, 2010

Transitioning a Rails app to Ruby 1.9.1

Ruby on Rails is a popular web application framework for the Ruby language. Now, Ruby is slow. Really, really slow. Really, amazingly, stunningly slow. It beats AppleScript and Emacs Lisp, but that's really about it.

Of course, this only matters rather rarely. The vast majority of web apps simply don't have to be fast. They just don't have enough users. Many can also benefit greatly from whole-page caching; something like a blog, for instance, can generally cache almost all of its output. And this, in fact, is usually the response that you get if you complain about Ruby's impressive slowness; "Oh, you can just cache, and anyway programmer time is more important than machine time!" This is true to an extent, but scant comfort if you have a highly dynamic site and are looking at using tens of machines to cater for your users.

Two years ago, Ruby 1.9 was released. It had the great advantage of being merely very, as opposed to mindbogglingly, slow, but it was unstable and had minimal library support, so was ignored by all. Then, a year ago, Ruby 1.9.1 was released; 1.9.1 is more stable, has better library support, and is considered usable for production purposes.

And yet, most Rails apps continue to use 1.8.x. As far as I can see, there are a few reasons for this. First, it was common wisdom that transitioning to 1.9 was very, very difficult when it became available two years ago. Second, Ruby has an interesting developer profile. Most people who write Ruby on Rails applications are not actually Ruby programmers. They are Rails programmers; they often know very little about the underlying language and will have considerable difficulty figuring out what's wrong when they see language errors. This, to an extent, probably applies to many web application developers, and we can now see the same thing happening with iPhone developers; many people just find some code that works on the Internet and slot it in.

As it turns out, upgrading even quite a large app can be pretty easy. You will, of course, need Ruby 1.9.1. It's quite likely that you'll have to compile this yourself; MacOS only comes with Ruby 1.8.7, and all but the most recent Linux distributions have 1.8.7 and 1.9.0 (which you definitely don't want). Compiling it is very easy, though; just get the source, and do:

./configure --program-suffix=19
make -j X
sudo make install


Where 'X' is the number of processors you have plus one. You don't have to bother with the -j thing, but it will speed things up considerably. You'll now have ruby19, irb19 and gem19, which are the 1.9.1 versions of ruby, irb and gem. From there, you can install your gem dependencies, and most of them will just work. There is more information on which gems will work here. Don't worry if you see people saying it doesn't work, as long as there are some people who say it does work.

Now, as long as you're using Rails 2.3.x, you can start your application and there's a good chance it'll work. You should do this:

ruby19 script/server


Or it'll use the wrong version of Ruby. You may, of course, need to update installed plugins. If you want to use console, you must do this:

ruby19 script/console --irb='irb19'


If you omit the irb thing it'll actually use the 1.8.x irb, causing much confusion.

Here are a few problems that you might run into.

It used to be pretty common, in ERBs (HTML templates) to do something like this:


<%= form_tag ({:action=>'bla'} ...


For a long time, Rails has warned about this, and people have happily ignored the warnings. Now, suddenly, it will no longer warn, simply produce a non-obvious error message. Lose the space before the opening bracket and all will be well.

Supers. Whereas before, when calling super in a method, it was perfectly acceptable to just call 'super', now you must provide arguments, or use 'super()' if there are none.

And then there are character encodings. If you have non-ASCII characters in a file, you must now put:

# -*- coding: utf-8 -*-


(or whatever the appropriate encoding is) at the top of the file.

There are no doubt other issues, but they're all generally soluble, and you should get a decent speed boost out of it.

The actual Rails maintainers would appear to be a little ahead of the 'community' here. For instance, if you are using I18n, the translation helper calls 'first' on translation keys. This 'first' is an extension on the String class defined by Rails. Until recently, it just looked at the string's underlying character array, which was reasonably efficient; a while ago it switched to using mb_char, which, on Ruby 1.8.x, is staggeringly inefficient; it results in the creation of a separate instance of a MultiByte class copying its data from the string. On every single translate call. Of which there might be a few hundred in a large complex page. On my laptop, simply calling 'first' on a string 10,000 times takes about 1.5 seconds. That means that if your page calls the translate function 100 times, which is not at all unreasonable, you're adding 15ms for each page view, not to mention allocating and throwing away lots of things, which exercises the slow garbage collector.

On Ruby 1.9, however, mb_char just returns self, so all is well (or not; other parts of translate are still very inefficient, but that is another story). I suspect that Rails will simply get slower and slower on 1.8.x until everyone is forced to switch.

But for the moment, no-one seems to want to. To me, this highlights a serious problem in the Rails community; not caring about speed to this extreme extent seems very unwise.

No comments:

Post a Comment