Dondo Land

Bryan Donovan's Weblog

All | General | Music | Rails

20080202 Saturday February 02, 2008

 Ruby: benchmarking ways to pass options to a method

I've wondered before what the fastest way is to pass a hash of options to a method in Ruby.. so today I benchmarked a few methods I've used in the past.

I've seen three main ways of passing an options hash to a method and extracting the options or use default values if they weren't passed in:

  • Merge the options with a hash of defaults, then assign values to local variables (or just use the options hash directly within the method)
  • Use something like var = options[:var] || 'default'
  • Use the delete method on the hash, e.g., var = options.delete(:var) || 'default'

From what I can tell, using delete is the fastest:

require 'benchmark'

def ops_delete(ops={})
  a = ops.delete(:a) || 11
  b = ops.delete(:b) || 22
  c = ops.delete(:c) || 33
  d = ops.delete(:d) || 44
end

def ops_merge(ops={})
  ops = {:a => 11, :b => 22, :c => 33, :d => 44}.merge(ops)
  a = ops[:a]
  b = ops[:b]
  c = ops[:c]
  d = ops[:d]
end

def ops_or(ops={})
  a = ops[:a] || 11
  b = ops[:b] || 22
  c = ops[:c] || 33
  d = ops[:d] || 44
end


n = 100000
puts "With no option values passed"
Benchmark.bmbm(7) do |x|
  x.report("delete:") { n.times do ops_delete; end }
  x.report("merge:")  { n.times do ops_merge;  end }
  x.report("or_nil:") { n.times do ops_or;     end }
end

puts
puts "With some option values passed"
Benchmark.bmbm(7) do |x|
  x.report("delete:") { n.times do ops_delete(:a => 5, :c => 2); end }
  x.report("merge:")  { n.times do ops_merge(:a => 5, :c => 2);  end }
  x.report("or_nil:") { n.times do ops_or(:a => 5, :c => 2);     end }
end

puts
puts "With all option values passed"
Benchmark.bmbm(7) do |x|
  x.report("delete:") { n.times do ops_delete(:a => 5, :b => 1, :c => 2, :d => 4); end }
  x.report("merge:")  { n.times do ops_merge(:a => 5, :b => 1, :c => 2, :d => 4);  end }
  x.report("or_nil:") { n.times do ops_or(:a => 5, :b => 1, :c => 2, :d => 4);     end }
end

Results (rehearsals omitted):

With no option values passed
              user     system      total        real
delete:   0.210000   0.000000   0.210000 (  0.209877)
merge:    0.720000   0.000000   0.720000 (  0.750394)
or_nil:   0.260000   0.000000   0.260000 (  0.258416)

With some option values passed
              user     system      total        real
delete:   0.250000   0.000000   0.250000 (  0.255536)
merge:    0.830000   0.000000   0.830000 (  0.831358)
or_nil:   0.310000   0.000000   0.310000 (  0.314737)

With all option values passed
              user     system      total        real
delete:   0.310000   0.000000   0.310000 (  0.306889)
merge:    0.900000   0.000000   0.900000 (  0.905261)
or_nil:   0.340000   0.000000   0.340000 (  0.348061)

This was done in Ruby 1.8.4 on a 2GHz MacBook Intel Core Duo with 1GB 667 MHz DDR2 SDRAM.



(2008-02-02 14:34:51.0/2008-02-02 14:34:51.0) Permalink Comments [4]
Trackback: http://blogs.sun.com/bdonovan/entry/ruby_benchmarking_ways_to_pass

Trackback URL: http://blogs.sun.com/bdonovan/entry/ruby_benchmarking_ways_to_pass
Comments:

Bryan,

I wrote a benchmarking blog post in October last year relating to the difference in code-level optimizations between Ruby 1.8.6 and Ruby 1.9.0 and how the roles were reversed in these two versions:
http://blog.managedopensource.com/2007/10/31/reversal-of-code-performance-optimizations-from-ruby-1-8-to-1-9

I also posted a direct response to your blog entry here showing that in Ruby 1.9.0 the results are different:
http://blog.managedopensource.com/2008/2/2/ruby-benchmark-options-passing-1.8-to-1.9

Posted by Susan Potter on February 02, 2008 at 04:12 PM PST #

Stumbled on your blog post and ran the same numbers in JRuby on soylatte Java 6 on basically the same machine. My numbers for Ruby 1.8.6p111 were roughly the same as yours.

Here's Ruby:

With no option values passed

user system total real
delete: 0.200000 0.000000 0.200000 ( 0.207049)
merge: 0.710000 0.010000 0.720000 ( 0.720514)
or_nil: 0.260000 0.000000 0.260000 ( 0.256742)

With some option values passed

user system total real
delete: 0.250000 0.000000 0.250000 ( 0.260502)
merge: 0.810000 0.010000 0.820000 ( 0.818434)
or_nil: 0.290000 0.000000 0.290000 ( 0.294220)

With all option values passed

user system total real
delete: 0.320000 0.000000 0.320000 ( 0.319335)
merge: 0.890000 0.010000 0.900000 ( 0.896330)
or_nil: 0.330000 0.000000 0.330000 ( 0.336673)

And here's JRuby:

With no option values passed

user system total real
delete: 0.133000 0.000000 0.133000 ( 0.133000)
merge: 0.318000 0.000000 0.318000 ( 0.319000)
or_nil: 0.474000 0.000000 0.474000 ( 0.474000)

With some option values passed

user system total real
delete: 0.170000 0.000000 0.170000 ( 0.169000)
merge: 0.295000 0.000000 0.295000 ( 0.295000)
or_nil: 0.145000 0.000000 0.145000 ( 0.146000)

With all option values passed

user system total real
delete: 0.154000 0.000000 0.154000 ( 0.154000)
merge: 0.289000 0.000000 0.289000 ( 0.288000)
or_nil: 0.101000 0.000000 0.101000 ( 0.100000)

Looks like something's artificially slowing down the "or_nil" case in the first scenario, but the rest of the numbers look pretty solid.

Posted by Charles Oliver Nutter on February 02, 2008 at 04:30 PM PST #

Thanks for the responses. Interesting to see the differences with 1.8.6, 1.9 and JRuby.

Posted by BryanDonovan on February 04, 2008 at 08:58 AM PST #

Oop..I noticed I labeled the "ops_or" method "or_nil".. that doesn't make sense.

Susan, the link to your response benchmarks doesn't work (probably the periods in the url), but I was able to view the RSS feed to read the results.

Posted by BryanDonovan on February 04, 2008 at 09:04 AM PST #

Post a Comment:

Name:
E-Mail:
URL:

Your Comment:

HTML Syntax: NOT allowed

« September 2008
SunMonTueWedThuFriSat
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    
       
Today


XML







Today's Page Hits: 3