Ruby performance: #attr_accessor vs. method definition

February 19, 2018

I’ve been digging a lot into Ruby performance lately and this much digging has taken me into some very interesting corners of Rubyland. My latest surprise has been the difference between defining attribute methods via the attr_accessor provided language construct (as well as attr_reader and attr_writer) vs. defining them yourself (as in def attribute and def attribute=). Here’s what I ran into…

I created two simple classes with identical interfaces. The only difference between the two is how we are defining access to set and get the @value attribute in each:

class TestClassAttrAccessor
  attr_accessor :value

  def initialize value
    @value = value
  end
end

class TestClassDefMethod
  def initialize value
    @value = value
  end

  def value
    @value
  end

  def value= value
    @value = value
  end
end

Then, I built a script using good ol’ benchmark-ips:

Benchmark.ips do |bm|
  with_attr = TestClassAttrAccessor.new 1
  with_method = TestClassDefMethod.new 1

  bm.report 'attr_accessor' do
    with_attr.value = 1
    with_attr.value
  end

  bm.report 'def method' do
    with_method.value = 1
    with_method.value
  end

  bm.compare!
end

And, to my surprise, these were the results:

...
Comparison:
       attr_accessor:  7039680.3 i/s
          def method:  5720331.2 i/s - 1.23x  slower

Yeah… I guess 1.23x is not that much of a difference, but similar to #has_key? vs. #[] this results in approximately 1.3 million more iterations per second, which becomes significant when building tries from large dictionaries.

Regardless, I’ll take it. From now on, I’ll use attr_accessor :<attribute> instead of def <attribute> whenever possible.

Also, I guess it may be time for me to get a slogan:

gonzedge: optimizing software 1.3 million iterations per second at a time

Hmm, doesn’t really have a nice ring to it 😕


Notes

  • I didn’t dig into it much, but I’m pretty sure it has to do with the fact that attr_accessor defines the methods via the rb_attr method in C.
  • Running a similar benchmark in Ruby 2.4.0, and there was a bigger gap:

    ...
    Comparison:
           attr_accessor:  6327686.3 i/s
              def method:  3932535.7 i/s - 1.61x  slower