Skip to content
This repository

AR Performance regression in 3.1 #1717

Closed
paul opened this Issue June 15, 2011 · 4 comments

4 participants

Paul Sadauskas Aaron Patterson John Hawthorn Jon Leighton
Paul Sadauskas
paul commented June 15, 2011
>> require 'active_record'
=> true
>> ActiveRecord::Base.establish_connection(
?>       :adapter => "sqlite3",
?>       :database  => "benchmark.db"
>>   )
>> ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS active_record_models")
>> ActiveRecord::Base.connection.execute("CREATE TABLE active_record_models (id INTEGER UNIQUE, title STRING, text STRING)")
>> class ActiveRecordModel < ActiveRecord::Base
>>   end
>> ActiveRecordModel.new
>> require 'benchmark'

# 3.0.7

>> Benchmark.measure { 100_000.times { ActiveRecordModel.new } }
=>   1.470000   0.000000   1.470000   1.474953

# 3.1.0.rc4

>> Benchmark.measure { 100_000.times { ActiveRecordModel.new } }
=>   7.910000   0.050000   7.960000   7.951169

>> Benchmark.measure { 100_000.times { ActiveRecordModel.new(:title => "foo", :text => "bar") } }
=>  15.380000   0.010000  15.390000  15.381160

https://gist.github.com/fabfaf1bd8503fbf6d32

John Hawthorn

About half of the runtime looks to be from the apply_default_scope assignment introduced in c69111b, which @jonleighton has said he would be changing.

Jon Leighton
Owner

Thanks @jhawthorn. I did mean to fix that, not sure how it escaped my attention. Have pushed a fix now, but leaving this open as master is still about twice as slow as 3-0-stable.

Aaron Patterson
Owner

Just to update this, it looks like we're making more (greater than 0) calls to scoped in 3.1. I'm looking in to this, but here are the perf graphs in the mean time.

Graph of 3.0:
3.0 graph

Graph of 3.1:

3.1 graph

Edit: added links to larger images.

Aaron Patterson
Owner

I think we can close this now. I used this script for benchmarking:

require 'active_record'
require 'benchmark'

p ActiveRecord::VERSION::STRING

ActiveRecord::Base.establish_connection(
  :adapter  => "sqlite3",
  :database => ":memory:"
)

ActiveRecord::Base.connection.execute("CREATE TABLE active_record_models (id INTEGER UNIQUE, title STRING, text STRING)")

class ActiveRecordModel < ActiveRecord::Base; end
ActiveRecordModel.new

N = 100_000
Benchmark.bm { |x| x.report('new') { N.times { ActiveRecordModel.new } } }

I got these results:

[aaron@higgins activerecord (3-1-stable)]$ ruby -I lib test.rb
"3.1.0.rc4"
      user     system      total        real
new  2.500000   0.010000   2.510000 (  2.527923)
[aaron@higgins activerecord (3-0-stable)]$ ruby -I lib test.rb
"3.0.9"
      user     system      total        real
new  2.490000   0.020000   2.510000 (  2.535049)

Times would vary slightly. Sometimes 3.1.0 would be faster, sometimes not. They are approximately the same speed, so I think we can close this ticket.

A few interesting things:

  • Asking for scope is very expensive. That is a major reason for the performance problems in this ticket. Creating AR objects within a scope will still be quite slow compared to non scope creation (but I think this is true for 3.0 too).
  • 3.1.0 creates 7 hashes per AR object where 3.0.x creates 4 (we have new caches in 3.1)
  • 3.1.0 makes more method calls from AR::Base#initialize
  • Some of these performance improvement commits could be applied to 3.0.x too

It took these commits to bring the time down:

e0fae72 remove useless assignment
d864616 lock_optimistically is typically true, so evaluate the common failure case first
558b5bb reduce object allocation during AR instantiation
196f92f remove the check for needs_type_condition? because ensure_proper_type will pick up the type column
992b3b5 stop using && for the short circuit side effect
5d954b4 let strings be converted to symbols inside the interpreter
9fd0d91 avoice paying hash cost if there are no serialized attributes
2fe088a cache column defaults for AR object instantiation
b927f0a AR object instantiation is ~30% faster in the simple case
0de56aa initialize instance variables
3a14e6f oops! remove debugging codes
0abb7b8 default create_with_value to a hash so we can eliminate conditionals, add test surrounding create_with(nil) behavior

@paul thank you for reporting this issue. I appreciate it! :-)

Aaron Patterson tenderlove closed this June 28, 2011
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.