1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Optimize ActiveResource::Base.new(attributes)

* Add performance benchmark similar to ActiveRecord
* Lazily find_or_create_resource_for_collection to not incur the overhead for empty arrays and arrays of primatives
* #duplicable? is faster than inline rescues when the object is not duplicable
* Don't constantly raise and handle NameError, raising is expensive
* Even when a resource is nested inside a module, always look inside the class first for the resource definition so we don't overwrite classes all the time

Before:                                     user     system      total        real
Model.new (instantiation)               0.120000   0.000000   0.120000 (  0.119961)
Nested::Model.new (instantiation)       0.150000   0.010000   0.160000 (  0.151183)
Model.new (setting attributes)         28.540000   0.680000  29.220000 ( 29.271775)
Nested::Model.new (setting attributes) 29.740000   0.580000  30.320000 ( 30.486210)

After:                                        user     system      total        real
Model.new (instantiation)                 0.120000   0.000000   0.120000 (  0.121249)
Nested::Model.new (instantiation)         0.150000   0.010000   0.160000 (  0.152429)
Model.new (setting attributes)           11.480000   0.170000  11.650000 ( 11.656163)
Nested::Model.new (setting attributes)   11.510000   0.210000  11.720000 ( 11.724249)
This commit is contained in:
Chris Griego 2011-05-01 23:05:42 -05:00
parent 31155eeb3c
commit a962bfe472
2 changed files with 94 additions and 15 deletions

View file

@ -0,0 +1,70 @@
require 'rubygems'
require 'active_resource'
require 'benchmark'
TIMES = (ENV['N'] || 10_000).to_i
# deep nested resource
attrs = {
:id => 1,
:name => 'Luis',
:age => 21,
:friends => [
{
:name => 'JK',
:age => 24,
:colors => ['red', 'green', 'blue'],
:brothers => [
{
:name => 'Mateo',
:age => 35,
:children => [{ :name => 'Edith', :age => 5 }, { :name => 'Martha', :age => 4 }]
},
{
:name => 'Felipe',
:age => 33,
:children => [{ :name => 'Bryan', :age => 1 }, { :name => 'Luke', :age => 0 }]
}
]
},
{
:name => 'Eduardo',
:age => 20,
:colors => [],
:brothers => [
{
:name => 'Sebas',
:age => 23,
:children => [{ :name => 'Andres', :age => 0 }, { :name => 'Jorge', :age => 2 }]
},
{
:name => 'Elsa',
:age => 19,
:children => [{ :name => 'Natacha', :age => 1 }]
},
{
:name => 'Milena',
:age => 16,
:children => []
}
]
}
]
}
class Customer < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000"
end
module Nested
class Customer < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000"
end
end
Benchmark.bm(40) do |x|
x.report('Model.new (instantiation)') { TIMES.times { Customer.new } }
x.report('Nested::Model.new (instantiation)') { TIMES.times { Nested::Customer.new } }
x.report('Model.new (setting attributes)') { TIMES.times { Customer.new attrs } }
x.report('Nested::Model.new (setting attributes)') { TIMES.times { Nested::Customer.new attrs } }
end

View file

@ -1239,9 +1239,10 @@ module ActiveResource
@attributes[key.to_s] = @attributes[key.to_s] =
case value case value
when Array when Array
resource = find_or_create_resource_for_collection(key) resource = nil
value.map do |attrs| value.map do |attrs|
if attrs.is_a?(Hash) if attrs.is_a?(Hash)
resource ||= find_or_create_resource_for_collection(key)
resource.new(attrs) resource.new(attrs)
else else
attrs.duplicable? ? attrs.dup : attrs attrs.duplicable? ? attrs.dup : attrs
@ -1251,7 +1252,7 @@ module ActiveResource
resource = find_or_create_resource_for(key) resource = find_or_create_resource_for(key)
resource.new(value) resource.new(value)
else else
value.dup rescue value value.duplicable? ? value.dup : value
end end
end end
self self
@ -1367,36 +1368,44 @@ module ActiveResource
end end
# Tries to find a resource in a non empty list of nested modules # Tries to find a resource in a non empty list of nested modules
# Raises a NameError if it was not found in any of the given nested modules # if it fails, then the resource is created
def find_resource_in_modules(resource_name, module_names) def find_or_create_resource_in_modules(resource_name, module_names)
receiver = Object receiver = Object
namespaces = module_names[0, module_names.size-1].map do |module_name| namespaces = module_names[0, module_names.size-1].map do |module_name|
receiver = receiver.const_get(module_name) receiver = receiver.const_get(module_name)
end end
const_args = RUBY_VERSION < "1.9" ? [resource_name] : [resource_name, false] const_args = RUBY_VERSION < "1.9" ? [resource_name] : [resource_name, false]
if namespace = namespaces.reverse.detect { |ns| ns.const_defined?(*const_args) } if namespace = namespaces.reverse.detect { |ns| ns.const_defined?(*const_args) }
return namespace.const_get(*const_args) namespace.const_get(*const_args)
else else
raise NameError create_resource_for(resource_name)
end end
end end
# Tries to find a resource for a given name; if it fails, then the resource is created # Tries to find a resource for a given name; if it fails, then the resource is created
def find_or_create_resource_for(name) def find_or_create_resource_for(name)
resource_name = name.to_s.camelize resource_name = name.to_s.camelize
ancestors = self.class.name.split("::")
if ancestors.size > 1
find_resource_in_modules(resource_name, ancestors)
else
self.class.const_get(resource_name)
end
rescue NameError
const_args = RUBY_VERSION < "1.9" ? [resource_name] : [resource_name, false] const_args = RUBY_VERSION < "1.9" ? [resource_name] : [resource_name, false]
if self.class.const_defined?(*const_args) if self.class.const_defined?(*const_args)
resource = self.class.const_get(*const_args) self.class.const_get(*const_args)
else else
resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base)) ancestors = self.class.name.split("::")
if ancestors.size > 1
find_or_create_resource_in_modules(resource_name, ancestors)
else
if Object.const_defined?(*const_args)
Object.const_get(*const_args)
else
create_resource_for(resource_name)
end
end
end end
end
# Create and return a class definition for a resource inside the current resource
def create_resource_for(resource_name)
resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base))
resource.prefix = self.class.prefix resource.prefix = self.class.prefix
resource.site = self.class.site resource.site = self.class.site
resource resource