attr vs method vs define_method

drbrain | Tue, 07 Mar 2006 00:59:00 GMT

Posted in

There are four different ways to define a method in Ruby. The two most common is the def keyword and the Module#attr family of methods. The last two ways use Module#define_method, define_method with a block and define_method with a Method object.

Ruby's interpreter handles methods created with each of these constructs differently and you can really notice it when benchmarking them:

                             user     system      total        real
attr_writer              0.880000   0.010000   0.890000 (  0.919265)
regular method           1.370000   0.000000   1.370000 (  1.485922)
define_method w/method   2.470000   0.010000   2.480000 (  2.636708)
define_method w/block    3.030000   0.020000   3.050000 (  3.268494)

Here's the benchmark code for the above output (I excluded the rehearsal run):

require 'benchmark'

class Foo
  attr_writer :ivar

  def attr=(val)
    @ivar = val
  end

  define_method :battr= do |val| @ivar = val end

  def make_dmethod
    self.class.send :define_method, :dattr=, method(:attr=)
  end
end

f = Foo.new
f.make_dmethod
n = 1_000_000

Benchmark.bmbm do |bm|
  bm.report 'attr_writer' do
    n.times { f.ivar = 1 }
  end

  bm.report 'regular method' do
    n.times { f.attr = 1 }
  end

  bm.report 'define_method w/method' do
    n.times { f.dattr = 1 }
  end

  bm.report 'define_method w/block' do
    n.times { f.battr = 1 }
  end
end

For the first method type, attr's family of methods, Ruby cheats and omits setting up a scope when calling the method. This accounts for the speed-up over a regular method.

Regular methods set up a scope then run all the code in the body.

When define_method is given a Method object (which requires some gymnastics to obtain) the method it creates points to the method held in the Method object. This indirection accounts for the slowdown shown.

When define_method is given a block Ruby has to perform all the setup for a yield in order to call the block. The block environment setup makes this the worst-performing way to create a method.

The second downside to creating a method using define_method and a block is everything referenced in the enclosing scope of the block will never be garbage collected.

Comments are disabled