This post originated from an RSS feed registered with Ruby Buzz
by Eric Hodel.
Original Post: attr vs method vs define_method
Feed Title: Segment7
Feed URL: http://blog.segment7.net/articles.rss
Feed Description: Posts about and around Ruby, MetaRuby, ruby2c, ZenTest and work at The Robot Co-op.
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.