From c879649a733d982fba9e70f5a280d13636b67c37 Mon Sep 17 00:00:00 2001 From: Eugene Kenny Date: Sun, 18 Jun 2017 15:37:06 +0100 Subject: Improve the performance of writing attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using a similar approach to 08576b94ad4f19dfc368619d7751e211d23dcad8, this change adds a new internal `_write_attribute` method which bypasses the code that checks for attribute aliases and custom primary keys. We can use this method instead of `write_attribute` when we know that we have the name of the actual column to be updated and not an alias. This makes writing an attribute with `attribute=` about 18% faster. Benchmark: ``` begin require "bundler/inline" rescue LoadError => e $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler" raise e end gemfile(true) do source "https://rubygems.org" gem "rails", github: "rails/rails" gem "arel", github: "rails/arel" gem "sqlite3" gem "benchmark-ips" end require "active_record" ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") ActiveRecord::Schema.define do create_table :posts, force: true do |t| end end class Post < ActiveRecord::Base end post = Post.new(id: 1) Benchmark.ips do |x| x.report("attribute=") { post.id = post.id + 1 } end ``` Before: Warming up -------------------------------------- attribute= 25.889k i/100ms Calculating ------------------------------------- attribute= 290.946k (± 3.1%) i/s - 1.476M in 5.077036s After: Warming up -------------------------------------- attribute= 30.056k i/100ms Calculating ------------------------------------- attribute= 345.088k (± 4.8%) i/s - 1.743M in 5.064264s --- .../lib/active_record/attribute_methods/primary_key.rb | 2 +- activerecord/lib/active_record/attribute_methods/write.rb | 14 ++++++++++---- activerecord/lib/active_record/inheritance.rb | 2 +- activerecord/lib/active_record/timestamp.rb | 4 ++-- 4 files changed, 14 insertions(+), 8 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index b9b2acff37..081aad434d 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -21,7 +21,7 @@ module ActiveRecord # Sets the primary key value. def id=(value) sync_with_transaction_state - write_attribute(self.class.primary_key, value) if self.class.primary_key + _write_attribute(self.class.primary_key, value) if self.class.primary_key end # Queries the primary key value. diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index 75c5a1a600..d7ddd35dff 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -17,7 +17,7 @@ module ActiveRecord generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 def __temp__#{safe_name}=(value) name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} - write_attribute(name, value) + _write_attribute(name, value) end alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= undef_method :__temp__#{safe_name}= @@ -36,8 +36,7 @@ module ActiveRecord end name = self.class.primary_key if name == "id".freeze && self.class.primary_key - @attributes.write_from_user(name, value) - value + _write_attribute(name, value) end def raw_write_attribute(attr_name, value) # :nodoc: @@ -46,10 +45,17 @@ module ActiveRecord value end + # This method exists to avoid the expensive primary_key check internally, without + # breaking compatibility with the write_attribute API + def _write_attribute(attr_name, value) # :nodoc: + @attributes.write_from_user(attr_name.to_s, value) + value + end + private # Handle *= for method_missing. def attribute=(attribute_name, value) - write_attribute(attribute_name, value) + _write_attribute(attribute_name, value) end end end diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 5776807507..1753322274 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -245,7 +245,7 @@ module ActiveRecord def ensure_proper_type klass = self.class if klass.finder_needs_type_condition? - write_attribute(klass.inheritance_column, klass.sti_name) + _write_attribute(klass.inheritance_column, klass.sti_name) end end end diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 55f3a194a9..ae50bc6bc1 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -86,7 +86,7 @@ module ActiveRecord all_timestamp_attributes_in_model.each do |column| if !attribute_present?(column) - write_attribute(column, current_time) + _write_attribute(column, current_time) end end end @@ -100,7 +100,7 @@ module ActiveRecord timestamp_attributes_for_update_in_model.each do |column| next if will_save_change_to_attribute?(column) - write_attribute(column, current_time) + _write_attribute(column, current_time) end end super(*args) -- cgit v1.2.3