From 31ba6fbb08c9c99419b77b7246c95285e1db1c16 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 13 Mar 2013 18:26:47 -0700 Subject: [PATCH] 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]