Friday, December 29, 2006

unshifty results...

Rick posted a comment and some thoughts on the validity of the performance numbers I posted a few months ago. He astutely noticed that the benchmarks for each language did slightly different things and generously offered up an excuse that all is fair in love and benchmarking. While it's tempting to be allowed to escape via this path, I think that correctness matters (even in benchmarks) and I was curious on whether the numbers would change with better Ruby code.

First, a comment. I was not knocking Ruby performance. My point at the time was that Ruby, being similar to Smalltalk, should be able to improve its performance using fairly well understood implementation techniques used in Smalltalk and many other OO language runtimes. As Ruby becomes more popular, I know there will be a focus on performance and Ruby developers will benefit. The comparisons here, just tell you where Ruby could be.

Two of the points Rick made, were 1) the benchmark code is wrong different between the languages and 2) the code doesn't use the proper Ruby array API, specifically calling out that using insert is overkill. I'm not a Ruby programmer yet despite all the pressure to take the Ruby plunge so I didn't notice the insert/pop discrepancy. Rick also noted that methods like push or unshift would be better for the type of work the benchmark was doing. The only way to find out is to bench it and besides, here I sit, on my couch, recovering from a brutal "cough your lungs out" cold with nothing better to do anyhow. This time, push/pop and shift/unshift are the candidates. I used the same original code from here with the loop counter changed to 500000 (so I don't have to wait 60 seconds+ per bench), and the inner operations changed to the various permutations of unshift/shift/push/pop.

unshift/shift 6.47 s
unshift/pop 6.97 s
push/pop 4.28 s
push/shift 6.05 s

Hmmmm, clearly the push/pop combination (which adds to the end and removes from the end) is the better choice for a pure stack and you would probably use push/shift for a queue. Ruby experts can chime in if there are better ways.

Now, let's try the old Smalltalk comparison. The equivalent methods in Smalltalk would be addFirst:/removeFirst/addLast:/removeLast. These numbers from same machine as above, same permutations using OrderedCollections.

addFirst/removeFirst 437 ms
addFirst/removeLast 468 ms
addLast/removeLast 375 ms
addLast/removeFirst 515 ms

So, for this case, I think Ruby has some opportunities to exploit which could help Ruby runtime performance. The one caveat is that I don't know enough about Ruby internals to tell if there is some super secret array semantics that makes it difficult to implement efficiently.

Can someone explain what Ruby is doing here that is taking the time ?