Wednesday, August 30, 2006

Binding and Benchmarking (or, What I Did for Lunch)

Today, Kent Sibilev posted a neat little trick over on his blog (which I was reading while I ate my lunch). His code allows you to replace code like this:


class Simple
  def initialize(foo, bar, baz)
    @foo = foo
    @bar = bar
    @baz = baz
  end
end

with code like this:

class Bound
  def initialize(foo, bar, baz)
    binding.local_to_instance
  end
end

This looks like a great time saver — at first blush (Kent does warn that it's not effecient). I tried it in a small script and it worked great. I can see myself doing this a lot in simple scripts, but will it work for a program that instantiates a lot of objects? Only you and your code know for sure, but I pulled out the Benchmark to see what it thought.

My first step was to wrap up the two versions of the class above, with some instantiating code in a benchmarking script like this:


class Binding
  def local_to_instance
    eval("local_variables").each do |name|
      eval("self").instance_variable_set("@#{name}", eval(name))
    end
  end
  
  alias :kernel_eval :eval
  def eval(code)
    kernel_eval(code, self)
  end
end

class Bound
  attr_accessor :foo, :bar, :baz
  
  def initialize(foo, bar, baz)
    binding.local_to_instance
  end
end


class Simple
  attr_accessor :foo, :bar, :baz
  
  def initialize(foo, bar, baz)
    @foo = foo
    @bar = bar
    @baz = baz
  end
end

require 'benchmark'

Benchmark.bmbm(10) do |x|
  x.report('simple') do
    for i in 1..100_000 do
      a = Simple.new(1,2,3)
    end
  end

  x.report('bound') do
    for i in 1..100_000 do
      a = Bound.new(1,2,3)
    end
  end 
end

Note: Kent's Binding class (which does all the magic) is up there in the code.

If you're not used to the Benchmark library, all I'm doing is setting up a 'benchmark with rehearsal' test (the whole test will run twice to provide cleaner results), with a couple of labels for the report.

Running the code above produces a report like this:


Rehearsal ---------------------------------------------
simple      0.400000   0.000000   0.400000 (  0.398161)
bound       2.620000   0.010000   2.630000 (  2.662697)
------------------------------------ total: 3.030000sec

                user     system      total        real
simple      0.140000   0.000000   0.140000 (  0.137859)
bound       2.520000   0.010000   2.530000 (  2.525122)

so, what does this prove? Well, Kent is right, his binding implementation isn't very effecient. On the other hand, it ripped through 100,000 instantiations of Bound objects in just two and a half seconds. I don't think that's liable to raise too many performance concerns. Maybe if you're creating tens of millions of objects it will start to get to you — or, maybe not. Try it. If it's too slow, profile it. If it's a problem, you can always go back to doing things the old fashioned way.

No comments: