From 31ba6fbb08c9c99419b77b7246c95285e1db1c16 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 13 Mar 2013 18:26:47 -0700 Subject: [PATCH 1/3] Optimised performance for attribute extraction Disabled all instrumentation unless enabled explicitly --- .gitignore | 1 + Rakefile | 5 ++++ bench/perf.rb | 52 ++++++++++++++++++++++++++++++++++ lib/active_model/serializer.rb | 43 ++++++++++++++++++++++++---- 4 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 bench/perf.rb diff --git a/.gitignore b/.gitignore index d87d4be6..f61f58b8 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ spec/reports test/tmp test/version_tmp tmp +*.swp diff --git a/Rakefile b/Rakefile index 0a1d1f00..23f17c4a 100644 --- a/Rakefile +++ b/Rakefile @@ -10,4 +10,9 @@ Rake::TestTask.new(:test) do |t| t.verbose = true end +desc 'Benchmark' +task :bench do + load 'bench/perf.rb' +end + task :default => :test diff --git a/bench/perf.rb b/bench/perf.rb new file mode 100644 index 00000000..af545cd8 --- /dev/null +++ b/bench/perf.rb @@ -0,0 +1,52 @@ +require "rubygems" +require "bundler/setup" +require "active_model_serializers" +require "active_support/json" +require "benchmark" + +class User < Struct.new(:id,:name,:age,:about) + include ActiveModel::SerializerSupport + + def fast_hash + h = { + id: read_attribute_for_serialization(:id), + name: read_attribute_for_serialization(:name), + about: read_attribute_for_serialization(:about) + } + h[:age] = read_attribute_for_serialization(:age) if age > 18 + h + end +end + +class UserSerializer < ActiveModel::Serializer + attributes :id, :name, :age, :about + + def include_age? + object.age > 18 + end +end + + + +u = User.new(1, "sam", 10, "about") +s = UserSerializer.new(u) + +u2 = User.new(2, "sam", 20, "about") +s2 = UserSerializer.new(u2) + +p s2.attributes + +p s.attributes + + +n = 100000 + +Benchmark.bmbm {|x| + x.report("init") { n.times { UserSerializer.new(u) } } + x.report("fast_hash") { n.times { u.fast_hash } } + x.report("attributes") { n.times { UserSerializer.new(u).attributes } } + x.report("serializable_hash") { n.times { UserSerializer.new(u).serializable_hash } } + x.report("serializable_hash_with_instrumentation") { n.times { UserSerializer.new(u).serializable_hash_with_instrumentation } } +} + + diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index ee5e917c..8ce64135 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -93,6 +93,11 @@ module ActiveModel end define_include_method attr + + if self.method_defined? :_fast_attributes + undef :_fast_attributes + end + end def associate(klass, attrs) #:nodoc: @@ -279,7 +284,15 @@ module ActiveModel # Returns a hash representation of the serializable # object without the root. - def serializable_hash + def serializable_hash_without_instrumentation + return nil if @object.nil? + @node = attributes + include_associations! if _embed + @node + end + + + def serializable_hash_with_instrumentation return nil if @object.nil? instrument(:serialize, :serializer => self.class.name) do @node = attributes @@ -290,6 +303,18 @@ module ActiveModel end end + # disable all instrumentation on serializable_hash (performance will be better) + def self.disable_instrumentation! + alias_method :serializable_hash, :serializable_hash_without_instrumentation + end + + # enable instrumentation for serializable_hash (performance may be impacted) + def self.enable_instrumentation! + alias_method :serializable_hash, :serializable_hash_with_instrumentation + end + + disable_instrumentation! + def include_associations! _associations.each_key do |name| include!(name) if include?(name) @@ -378,13 +403,19 @@ module ActiveModel # Returns a hash representation of the serializable # object attributes. def attributes - hash = {} + _fast_attributes + rescue NameError + method = "def _fast_attributes\n" - _attributes.each do |name,key| - hash[key] = read_attribute_for_serialization(name) if include?(name) - end + method << " h = {}\n" - hash + _attributes.each do |name,key| + method << " h[:#{key}] = read_attribute_for_serialization(:#{name}) if send #{INCLUDE_METHODS[name].inspect}\n" + end + method << " h\nend" + + self.class.class_eval method + _fast_attributes end # Returns options[:scope] From 08a182d74307c77470ac17064707dca5d983a6af Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 13 Mar 2013 22:18:40 -0700 Subject: [PATCH 2/3] per discussion remove instrumentation altogether --- bench/perf.rb | 9 --------- lib/active_model/serializer.rb | 26 +------------------------- 2 files changed, 1 insertion(+), 34 deletions(-) diff --git a/bench/perf.rb b/bench/perf.rb index af545cd8..ea668d56 100644 --- a/bench/perf.rb +++ b/bench/perf.rb @@ -31,14 +31,6 @@ end u = User.new(1, "sam", 10, "about") s = UserSerializer.new(u) -u2 = User.new(2, "sam", 20, "about") -s2 = UserSerializer.new(u2) - -p s2.attributes - -p s.attributes - - n = 100000 Benchmark.bmbm {|x| @@ -46,7 +38,6 @@ Benchmark.bmbm {|x| x.report("fast_hash") { n.times { u.fast_hash } } x.report("attributes") { n.times { UserSerializer.new(u).attributes } } x.report("serializable_hash") { n.times { UserSerializer.new(u).serializable_hash } } - x.report("serializable_hash_with_instrumentation") { n.times { UserSerializer.new(u).serializable_hash_with_instrumentation } } } diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 8ce64135..f1fbae2a 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -284,37 +284,13 @@ module ActiveModel # Returns a hash representation of the serializable # object without the root. - def serializable_hash_without_instrumentation + def serializable_hash return nil if @object.nil? @node = attributes include_associations! if _embed @node end - - def serializable_hash_with_instrumentation - return nil if @object.nil? - instrument(:serialize, :serializer => self.class.name) do - @node = attributes - instrument :associations do - include_associations! if _embed - end - @node - end - end - - # disable all instrumentation on serializable_hash (performance will be better) - def self.disable_instrumentation! - alias_method :serializable_hash, :serializable_hash_without_instrumentation - end - - # enable instrumentation for serializable_hash (performance may be impacted) - def self.enable_instrumentation! - alias_method :serializable_hash, :serializable_hash_with_instrumentation - end - - disable_instrumentation! - def include_associations! _associations.each_key do |name| include!(name) if include?(name) From 710c375088bfaa8f9a14177675b51cc91a94a020 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 14 Mar 2013 14:11:04 -0700 Subject: [PATCH 3/3] remove safe guard per discussion with steveklabnik --- lib/active_model/serializer.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index f1fbae2a..93b731fa 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -94,10 +94,6 @@ module ActiveModel define_include_method attr - if self.method_defined? :_fast_attributes - undef :_fast_attributes - end - end def associate(klass, attrs) #:nodoc: