Tuesday, October 16, 2007

SICP 1.1.6 (Ruby) A look back

In my post on SICP 1.1.6, there were a couple of questions raised about my Ruby code.

Fabio Akita Your first example would be better written off as:

def abs(num)
num < 0 ? -num : num
end

Personally, I really hate the ternary if, I’ve always thought that it makes code harder to read. It’s probably still worth looking at though, since my biases are just that.

Peter Cooper ... I was just intrigued as to why you wouldn’t use idiomatic Ruby, when SICP tends to use Scheme very idiomatically.

This is another one of my failings, I’ve been doing enough non-Ruby stuff lately that I wasn’t thinking in it. Couple that with sitting down to ‘translate’ the code in separation from reading the text, and I was set to really mess up. Hopefully I can correct this going forward.

In any case, it’s worth looking at how well the various versions of abs perform in Ruby, so here’s a simple benchmarking script to try

def tern_abs (a)
  a < 0 ? -a : a
end

def explicit_abs (a)
  if a < 0
    a = -a
  end
  a
end

def implied_abs (a)
  if a < 0
    -a
  else
    a
  end
end

require 'benchmark'

n = 1_000_000

Benchmark.bm(15) do |x|
  x.report("ternary:")  { n.times do ;tern_abs(-5);tern_abs(5); end }
  x.report("explicit:") { n.times do ;explicit_abs(-5);explicit_abs(5); end }
  x.report("implied:")  { n.times do ;implied_abs(-5);implied_abs(5); end }
  x.report("system:")   { n.times do ;-5.abs;5.abs; end }
end

And here are the results of running it:

ruby benchmarking_abs.rb 
                     user     system      total        real
ternary:         0.550000   0.000000   0.550000 (  0.558707)
explicit:        0.330000   0.000000   0.330000 (  0.324859)
implied:         0.320000   0.000000   0.320000 (  0.322541)
system:          0.240000   0.000000   0.240000 (  0.241916)

It turns out that (at least in this case) the ternary form is the least performant. It’s probably no surprise that the built in version is the fastest. From now on, I’ll stick to using the built in abs.

Just for grins, here’s the result of the benchmark when I run it on JRuby:

$ jruby -v
ruby 1.8.5 (2007-08-23 rev 4201) [i386-jruby1.0.1]
$ jruby benchmarking_abs.rb
                    user     system      total        real
ternary:         1.768000   0.000000   1.768000 (  1.767000)
explicit:        1.734000   0.000000   1.734000 (  1.734000)
implied:         1.729000   0.000000   1.729000 (  1.730000)
system:          0.716000   0.000000   0.716000 (  0.716000)

It looks like the system is an even better deal compared to the ‘roll your own’ versions, though there appears to be less of a difference between them. (Any JRubyists out there care to venture a guess as to why that might be?)

3 comments:

Kevin said...

Ternary is more terse, but less readable. In actually implementing the method (not using the functionality inline), i'd always use the implicit form. If you're going to the trouble to define a method, it should be immediately clear. Saving a couple lines doesn't matter.

When writing the functionality inline as part of a larger method, I'd consider using the ternary expression, but still shy away from it. There's few places that have good reason to use it, if any.

zenspider said...

I don't think you're using a big enough N. Switching to 10m shows (on my system):

ternary: 10.790000
explicit: 11.670000
implied: 10.940000
system: 3.740000

From an AST perspective, there is ZERO difference between ternary and implied. If there is any runtime difference, I don't see how.

I'm using:

ruby 1.8.6 (2007-07-14 patchlevel 5000) [i686-darwin8.10.1]

Peter Cooper said...

On the standard Ruby that comes with Leopard, I get:

ternary: 2.340000
explicit: 2.470000
implied: 2.340000
system: 0.290000

(similar results on multiple runs). Ternary and implied run in the same time on each run. I'm happy about this as I was beginning to worry that ternary operations were somehow inefficient in Ruby :)

On JRuby 1.1b1 I get:

ternary: 0.839000
explicit: 0.840000
implied: 0.840000
system: 0.377000

which blows my mind, but even at 10m N I get almost identical results for ternary / explicit / implied. Interesting!