Ruby hero Erik Michaels-Ober gave a superbly userful talk (video, slides) at this year’s Barcelona Ruby Conference. In it, Michaels-Ober offers up a dozen-or-so cases where some casual Ruby code can be made both faster and cleaner by using built-in Ruby features.
This sometimes means using one method instead of two: for example, instead of
some_array.map(&:method).flatten Ruby offers
some_array.flat_map(&:method). And sometimes it means using one method in lieu of another, such as
string.tr('_', '-') instead of
string.gsub('_', '-'). As if to prempt concerns that this is old news, Michaels-Ober accompanies many of his slides with pull requests to major projects like Rails and Rubinius to fix where the slower, less clear alternatives are still being used.
Michaels-Ober begins with a discussion of his benchmarking methodology, recommending a gem called benchmark-ips which extends Ruby’s benchmark library to show iterations-per-second instead of seconds-per-itaration. This gives you a simple bigger-is-better result and keeps you from having to guess how many iterations you’ll need for the result to be meaningful. Now that benchmarking is a part of my toolkit, I can definitely see the usefulness of this gem.
I would definitely recommend you watch the talk (45 min), but if you’d like the tl;dw version (and for my own future reference), here are the key insights:
- Within a method that takes a block,
yieldis 5x faster than
- Using Symbol#to_proc (e.g.
&:method) is 20% faster than using a block
.flat_mapis 4x faster than
.reverse_eachis 17% faster than
.each_keyis 33% faster than
.sampleis 15x faster than
- Hash#merge! is 3x faster than Hash#merge (as long as you don’t need immutable state)
- Passing a block as the second argument to
.fetchis 2x faster than passing the block’s result directly
.subis 50% faster than
.gsubif you know you’ll be making only one replacement
.tris 5x faster than
.gsubif you don’t need regular expressions
- sequential assignment (e.g.
a = 1; b = 2;) is 40% faster than parallel assignment (e.g.
a, b = 1, 2)
- Exceptions are quite slow. If you don’t know if an objects responds to a method, checking
.respond_to? :methodin an
ifblock is 10x faster than rescuing
Of course, as the speaker makes clear, the speed comparisons are sensitive to the particular use case so your mileage may vary, but in every case the faster option will still be faster and in most of these cases, more intuitive.