Skip to content

Instantly share code, notes, and snippets.

@pjb3
Created October 16, 2010 11:56
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pjb3/629716 to your computer and use it in GitHub Desktop.
Save pjb3/629716 to your computer and use it in GitHub Desktop.
Apparently attr_reader is faster than a method in all ruby implementations. Why?
require 'benchmark'
puts
puts ENV["RUBY_VERSION"]
class A
def initialize(foo)
@foo = foo
end
def foo
@foo
end
end
class B
attr_reader :foo
def initialize(foo)
@foo = foo
end
end
a = A.new "foo"
b = B.new "foo"
Benchmark.bmbm do |x|
x.report("method") { 1_000_000.times { a.foo } }
x.report("attr_reader") { 1_000_000.times { b.foo } }
end
$ rvm ruby method_vs_attr_reader.rb
jruby-1.5.2
Rehearsal -----------------------------------------------
method 0.241000 0.000000 0.241000 ( 0.193000)
attr_reader 0.082000 0.000000 0.082000 ( 0.082000)
-------------------------------------- total: 0.323000sec
user system total real
method 0.060000 0.000000 0.060000 ( 0.060000)
attr_reader 0.050000 0.000000 0.050000 ( 0.050000)
rbx-1.1.0-20100923
Rehearsal -----------------------------------------------
method 0.242164 0.001986 0.244150 ( 0.139181)
attr_reader 0.089972 0.000132 0.090104 ( 0.077365)
-------------------------------------- total: 0.334254sec
user system total real
method 0.050219 0.000019 0.050238 ( 0.050255)
attr_reader 0.036286 0.000028 0.036314 ( 0.036333)
ree-1.8.6-20090610
Rehearsal -----------------------------------------------
method 0.190000 0.000000 0.190000 ( 0.189835)
attr_reader 0.130000 0.000000 0.130000 ( 0.131267)
-------------------------------------- total: 0.320000sec
user system total real
method 0.180000 0.000000 0.180000 ( 0.182960)
attr_reader 0.140000 0.000000 0.140000 ( 0.136712)
ree-1.8.7-2010.02
Rehearsal -----------------------------------------------
method 0.220000 0.000000 0.220000 ( 0.219702)
attr_reader 0.120000 0.000000 0.120000 ( 0.126708)
-------------------------------------- total: 0.340000sec
user system total real
method 0.210000 0.000000 0.210000 ( 0.208938)
attr_reader 0.140000 0.000000 0.140000 ( 0.133091)
ruby-1.9.2-p0
Rehearsal -----------------------------------------------
method 0.130000 0.000000 0.130000 ( 0.127519)
attr_reader 0.100000 0.000000 0.100000 ( 0.098922)
-------------------------------------- total: 0.230000sec
user system total real
method 0.130000 0.000000 0.130000 ( 0.131623)
attr_reader 0.100000 0.000000 0.100000 ( 0.102913)
@tenderlove
Copy link

There a special optimizations done for attr_*. The way MRI works is it stores the parsed code as a tree, then when executing, it walks the tree. In the case of normal methods like def foo; @foo end, MRI must do lots of work like set up stack frames, check for stack overflow, set up the environment (like access to class variables, etc). With attr_*, the interpreter knows in advance that it's simply an ivar lookup (which is basically a hash lookup).

Take a look here to see how MRI evaluates attr_* methods, then here for normal methods. If you follow the function calls for normal methods, you'll step in to vm_setup_method and you can see how much work it is to call a normal method. Compare that to the attr_* method calls and you'll see why attr_* is so much faster.

I'm not familiar with the internals of the other implementations, but I suspect they have similar optimizations. Hope that helps!

@bcardarella
Copy link

Very cool, thanks for the explanation!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment