Book Review: Writing Efficient Ruby Code

The second shortcut from Addison-Wesley Professional series I'm going to review is called Writing Efficient Ruby Code. A very promising title, especially considering that this book is only 50 pages long.

As usual, this shortcut can be intended as a sort of programmer-friendly detailed cheatsheet: like the other ones in this series it sports a monitor-friendly landscape layout and does not go to deep into the details unless strictly necessary to understand a particular concept.

The Author

Dr. Stefan Kaes, the author, contributed a lot to improve Ruby on Rails' performance by refactoring portions of its core and try to “get maximum speed out of performance-critical sections of code”. This short but interesting shortcut groups together a lot of performance tweaks, tips and tricks but also some “anti-patterns” Kaes was able to identify through his career as programming teacher Ruby software consultant and key Rails contributor.

The Contents

Like with the previously-covered Mongrel shortcut, Writing Efficient Ruby Code always goes straight to the point when it comes to identify problems. The first one mentioned is of course that the Ruby Interpreter is Slow, most people are aware of that, due to their direct experience or because this argument is normally used by non-Rubyists to argue the language's usability in commercial projects. What you may not know is why that is so, and that's where the first part of this book comes into play.

“Ruby is a highly dynamic language: Almost all language entities are first-class citizens in that they can be created, changed, and destroyed at runtime. This comprises classes, modules, methods, constants, and class and instance variables. Only local variables are second-class citizens in Ruby: Whether a name refers to a local variable is determined at parse time.

This makes Ruby extremely flexible, but also more complex. Whever you use a name to refer to an object, Ruby has to search for the object it refers to, and this costs in terms of processing time.

As a matter of fact, one of the most recurring tips in the book to improve code performance is the following:

Method calls are expensive, use variables directly when possible.

Keep this in mind: self.something is not the same as @something. The end result is the same, but the first way costs more in terms of performance because Ruby has to look up the method name.
Similarly, local variables should be introduced as a way to “cache” the result of method calls. Often you may feel “guilty” to introduce a new variable and keep calling the same method over and over: this should definitely be avoided.

Other useful tips include, for example:

  • Use syntax constructs (e.g. assignments) as expressinons. Use evaluation precedences.
  • Use interpolated strings "... #{string_variable}" (there's also no performance difference if constant strings are used between " or ')
  • Use operators which update the data structure without copying it (when possible). Use update or merge to update hashes.
  • Iterating using for a in A is slightly faster than performing the same iteration using each, (it is the opposite in Ruby 1.9 though)
  • do not use return unless you have to
  • test in order of expected case frequency
  • Use parallel assignment (a, b = 5, 6) where applicable
  • If a module gets included in only one other class (or module), it’s preferable to open the class instead.

I deliberately chose not to elaborate any further on the tips listed above because otherwise I'll give a big chunk of the contents of the book itself. If you know Ruby enough, you may already know why such reccommendations make sense, but if you don't, Writing Efficient Ruby Code can be a short but very interesting read.

The Good

For each of the 30 “coding patterns” (and consequent anti-patterns) described in the book, the author does a great job explaining the reasons of doing something in a particular way, also through examples and benchmarks, where possible.

Furthermore, this shortcut can really be useful to grasp a few difference between Ruby 1.8.5, 1.8.6 and 1.9 in terms of performance: not all the patters apply to all Ruby implementations, and when that's the case it is clearly stated.

The Bad

My only complaint about the book is probably the lack of details and more “specialized” patterns. Everything (except for a few Rails-specific tips) normally apply to Ruby as a whole, without going deeply to analyze specific libraries or third-party gems. As a result, once you get the general idea, some of the patters may seem pretty obvious or a logic consequence of others.

It is also true that this is meant to be a shortcut, not a comprehensive analysis on code optimization techniques which can be applied to specific cases: something like this would require much more than 50 pages!

The Bottom Line

Read it, re-read a few bits of it to make sure you grasp the most important concepts, and keep its table of contents in front of you as a reminder when refactoring your code!