Skip to content

Instantly share code, notes, and snippets.

@mariochavez
Last active December 18, 2015 15:08
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save mariochavez/eb6060e04a9daadfc134 to your computer and use it in GitHub Desktop.
Save mariochavez/eb6060e04a9daadfc134 to your computer and use it in GitHub Desktop.
Post for Engine Yard

Ruby 2.0

It has been now almost 4 months since Ruby 2.0 was released. Upgrading to Ruby 2.0 coming from 1.9.3 is quite more simple than the upgrade from Ruby 1.8 to 1.9.

Ruby 2.0 introduces new functionality and internal changes focused on improve Ruby's performance.

So, lets see what Ruby 2.0 brings to us. This will not be a recount on all the features/changes but I will list a few changes with great impact.

Improved method dispatch

The method dispatch mechanism is quite complex and requires a lot of work to be done in order to execute instructions. This involves - simplified version -:

  • Method lookup in receiver and ancestors
  • Verify method parameters arity
  • Check method visibility
  • Setup a control frame
  • Setup up of local environment
  • Bytecode execution

Since version 1.9 with the introduction of Ruby's VM - YARV -, optimization techniques where implemented to improve method dispatch, the first two techniques listed below were introduced with 1.9, the last two came with 2.0.

Specialized instructions

Specialized instructions are micro optimizations where the original call is replaced with fast optimized instructions, this change allows to eliminate the dispatch cost because its a sort of shortcut.

But this technique is limited to a certain instructions that can take advantage of this, unfortunately it can't be applied to a wide range of instructions.

Method Caching

With Ruby we have two different method caching, one is the inline cache which is used to cache the method lookup for a specific caller site and the second one is the global cache.

Cache helps in reduce method lookup overhead, instead of searching throught the receiver and ancestors, a check is done first in the global cache, then Ruby looks in the inline cache, the final option is to perform the actual method lookup, this last one can happen if method has never been called or cache was invalidated.

Cache check results

Starting from Ruby 2.0, checks for parameters arity and method visibility are stored into the inline cache after first check, increase performance happens due to the fact of not having to perform those checks again if Ruby already have it in cache.

Frameless CFUNC methods

It turns out that several CFuncs do not need to have a control frame setup, for example a call to String#length only returns the size of the given string but does not need to know anything else about its environment, a performance gain comes from not having to built a frame for CFunc that will never use it.

GC Optimizations

Bitmap marking

A new Garbage collector was introduced with Ruby 2.0, it's called Bitmap marking. From now on, each Ruby heap has a corresponding bitmap - an aligned memory structure -. GC, before 2.0, used to have a flag inside RValue structures, this flag was use to mark a structure that was still referenced in our program.

The big change from Ruby 1.9 GC is that the RValue structure does not contains this flag anymore, so when the GC process set or clear the mark, our RValue structures are not modified, since this flag is now keep in the Bitmap associated with the heap.

This allows Ruby 2.0 to introduce a feature called Copy On Write.

Copy On Write

Copy On Write or simply COW allows to reduce Ruby's process memory usage when a process is forked. Instead of duplicate the memory space on each process fork, COW allows multiple processes to share the same memory - due to the fact that GC does not modify RValue structures anymore -, until one of the process modifies shared data. In some way we can see this like a lazy memory copy.

Unicorn's webserver processes on a Rails application can be an example of COW benefit.

In some cases this would reduce the memory consumption for Ruby 2.0 programs and allow more processes working with less memory usage.

Faster Kernel#require

For a while there have been discussions on how to improve Kernel#require method performance, specifically for Rails applications.

Over the past years several solutions have been offered to improve Rails boot. With Ruby 2.0 release an improvement has been done on Kernel#require that helps to improve Rails boot.

Here is a quick test - not scientific - that shows the difference between booting the Rails application with Ruby 1.9.3-p392 and Ruby 2.0p185.

Ruby 1.9

$ time bundle exec rake environment
bundle exec rake environment  7.21s user 1.26s system 96% cpu 8.748 total

Ruby 2.0

$ time bundle exec rake environment
bundle exec rake environment  4.60s user 1.18s system 91% cpu 6.289 total

My sample application is quite simple, so if you try by your own - maybe on a big Rails application - your mileage may vary.

Conclusions

Ruby 2.0 is a great improvement from earlier versions. It sure has a lot of eye candy features, but most important features, are the ones related to improve performance for our web applications.

If you are at Ruby 1.9 the jump to 2.0 may be easy. If you are still at Ruby 1.8, the end of life is close, so is better if you act now to upgrade your Ruby.

As an additional push to switch to Ruby 2.0 is that Rails 4.0 has dropped support for Ruby 1.8 and Sinatra 2.0 might do it as well.

Ruby 2.1 roadmap has been announced, and chances are that it will be available around the end 2013, for now we only know that it may include a Generational GC, which may bring even more performance improvements to Ruby.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment