Jay Fields has a nice post about the goodness of Enumerable#inject in Ruby. Like anyone who enjoys functional programming, I appreciate the concept behind inject and its possible usage. Your language may implement it with a method called inject, a function fold or reduce, but the concept is the same. I think that there is nothing inherently evil about using Enumerable#inject in your code, however one must be very careful and question whether there are simpler alternatives available. Abuse of creative usage of inject or cleverness is often paid for in terms of the readability of the code and can’t be discouraged enough. Another good reason not use inject is when performance counts and we are dealing with large datasets. In Ruby 1.8 in fact, inject is much slower than an equivalent solution implemented with the each iterator or other available methods. People will tell you that this doesn’t matter, but in the real world, having a method take twice as long because you opted for inject rather than each, is not always justifiable or acceptable.

For example, the following snippet benchmarks four different methods for summing the first n natural numbers. It benchmarks them for n = 10, 102, 103, 104, 105, 106, 107 and 108. Please note that the problem at hand can be solved arithmetically, like Gauss did when he was 10, but it will serve us well to test differences in the raw speed of the compared methods provided by Ruby.

The sum_with_while is admittedly ugly, and entirely not idiomatic. No Rubyist would write the method in that way. It’s there though, in order to have a non-iterator, loop-based solution which doesn’t rely on methods from the Enumerable class. It will give us some perspective. Also, the choice of reopening the Integer class, rather than writing methods that accept n as an argument, is entirely arbitrary and doesn’t affect the benchmark in any way.

These results are not surprising. The ugly sum_with_while method is just slightly faster than the sum_with_inject one, while sum_with_each and sum_with_times are significantly faster than sum_with_inject. For n sufficiently large, each is (linearly) about 1.5 times faster than inject. Again, nothing new, if you have done some number crunching with Ruby, you already know a few tricks and the fact that inject is notoriously slow.

Just out of curiosity, I decided to see how things have improved with Ruby 1.9. Here comes the surprise: ruby 1.9.0 (2008-03-21 revision 15825) [i686-darwin9.2.0] yields the following results.

Look carefully, the good news is that while has been sped up by a factor of 3. You may notice that the execution time of the methods which employ inject, each and times are essentially the same in Ruby 1.9. This could be considered good news too, if we didn’t take a look at the previous output. All three methods in Ruby 1.9 are 2 to 3 times slower than in Ruby 1.8. each and times were both faster than while and are now 10 times slower! Forget about inject for a second, which after all can be avoided; but idiomatic Ruby code uses each iterators all over the place. Am I missing something, or Houston, do we have a problem? This loss of performance, if confirmed, should be considered as a bug.

To expose the extent of this regression, let’s see how JRuby 1.1RC3 (with the -J-server option enabled) performs:

Conclusion: the inject, each and times methods are much slower in Ruby 1.9 on Mac. As you can see from the comments, this seems to be caused by the fact that the function sigsetjmp() was introduced during revision 15124 and it happens to be much slower on Mac OS X than it is on Linux.

Get more stuff like this

Subscribe to my mailing list to receive similar updates about programming.

Share this:

Related

About The Author

Antonio Cangiano

Antonio Cangiano is a Software Development Manager at IBM. He authored Ruby on Rails for Microsoft Developers (Wrox, 2009) and Technical Blogging (The Pragmatic Bookshelf, 2012, 2019). He is also the Marketing Lead for Cognitive Class, an educational initiative which he helped grow from zero to over 1 Million students. You can follow him on Twitter.

20 Comments

Try the 1.9.0 that was released on Christmas. inject is faster than both 1.8.6 and the version of 1.9.0 that you used. The fact that inject has gotten much slower between those releases at least tells us something.

I wrote a script to find when this happened. On my mac, using this inject benchmark I found on the Ruby talk mailing list (http://pastie.caboo.se/170752), the runtime jumps from 3 seconds to 35 seconds at revision 15124. The diff between 15123 and 15124 can be seen here: http://pastie.caboo.se/170751

1) Are you running the script on Linux/Windows or Mac? The problem appears to be a Mac only issue;
2) The version of Ruby 1.9 that introduces the performance problem is a recent one, not the one that was released at Christmas that you tested;
3) Did you run JRuby with the -J-server option and with the latest version of the JDK?