Accessing ActiveRecord attribute slower than local variable
I was deep in a performance optimization flow when I stumbled upon this part in a ruby-prof graph:
- 17.59% (73.45%) SomeTable::GeneratedAttributeMethods#some_attribute [30263106 calls, 44665545 total]
- 15.24% (86.62%) ActiveRecord::AttributeMethods::Read#_read_attribute [30263106 calls, 49437307 total]
- 11.82% (77.58%) ActiveModel::AttributeSet#fetch_value [30263106 calls, 49068748 total]
- 5.44% (46.06%) ActiveModel::AttributeSet#[] [30263106 calls, 49303667 total]
- 2.32% (42.58%) Hash#[] [30263106 calls, 72892138 total]
- 2.05% (17.36%) ActiveModel::Attribute#value [30263106 calls, 49274896 total]
ActiveRecord stores attributes internally in a hash, which is more expensive than I expected.
Naturally, I benchmarked this to come up with a faster access method. I tried various alternative methods and although sources on the internet suggested some might be faster, no ActiveRecord-native way of accessing attributes was faster than the standard method.
This gist contains the benchmark I ran.
The only way I could speed up access was by caching the value in a local instance variable, like this:
class Records < ActiveRecord::Base
def ivar_some_string
@ivar_some_string ||= _read_attribute('some_string')
end
end
I tested this on arm64-darwin25 and x86_64-linux, and it turns out that instance variable lookups
are 2 to 3 times faster than standard ActiveRecord attribute access.
I would not generally recommend this optimisation, but in some edge cases, it might be worth a try.
Here are the relevant snippets from the output of the benchmark script:
Records#1 some_string="123456"
Ruby: ruby 4.0.4 (2026-05-12 revision b89eb1bcbf) +PRISM [arm64-darwin25]
ActiveRecord: 8.1.3
ruby 4.0.4 (2026-05-12 revision b89eb1bcbf) +PRISM [arm64-darwin25]
Calculating -------------------------------------
some_string (public accessor)
4.880M (± 1.1%) i/s (204.94 ns/i) - 24.418M in 5.004799s
ivar_some_string (memoized ivar)
15.393M (± 1.9%) i/s (64.96 ns/i) - 78.333M in 5.090594s
Comparison:
some_string (public accessor): 4879503.2 i/s
ivar_some_string (memoized ivar): 15393424.8 i/s - 3.15x faster
Fun fact, in 65 nanoseconds, light travels 20 meters, and a 3GHz CPU performs 200 cycles.