aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
authorSean Griffin <sean@seantheprogrammer.com>2015-09-24 14:03:27 -0600
committerSean Griffin <sean@seantheprogrammer.com>2015-09-24 14:06:59 -0600
commit136fc65c9b8b66e1fb56f3a17f0d1fddff9b4bd0 (patch)
tree88df6c3943bc23df8d5acf748cb8c02f9221f376 /activerecord/lib
parent8a811c83aa386e4ad26553c913d1a450d8a98d5f (diff)
downloadrails-136fc65c9b8b66e1fb56f3a17f0d1fddff9b4bd0.tar.gz
rails-136fc65c9b8b66e1fb56f3a17f0d1fddff9b4bd0.tar.bz2
rails-136fc65c9b8b66e1fb56f3a17f0d1fddff9b4bd0.zip
Improve the performance of `save` and friends
The biggest source of the performance regression in these methods occurred because dirty tracking required eagerly materializing and type casting the assigned values. In the previous commits, I've changed dirty tracking to perform the comparisons lazily. However, all of this is moot when calling `save`, since `changes_applied` will be called, which just ends up eagerly materializing everything, anyway. With the new mutation tracker, it's easy to just compare the previous two hashes in the same lazy fashion. We will not have aliasing issues with this setup, which is proven by the fact that we're able to detect nested mutation. Before: User.create! 2.007k (± 7.1%) i/s - 10.098k After: User.create! 2.557k (± 3.5%) i/s - 12.789k Fixes #19859
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb12
-rw-r--r--activerecord/lib/active_record/attribute_mutation_tracker.rb29
2 files changed, 39 insertions, 2 deletions
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index a439683185..24f0ccb239 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -51,12 +51,12 @@ module ActiveRecord
end
def changes_applied
- super
+ @previous_mutation_tracker = @mutation_tracker
store_original_attributes
end
def clear_changes_information
- super
+ @previous_mutation_tracker = nil
store_original_attributes
end
@@ -89,6 +89,10 @@ module ActiveRecord
end
end
+ def previous_changes
+ previous_mutation_tracker.changes
+ end
+
def attribute_changed_in_place?(attr_name)
@mutation_tracker.changed_in_place?(attr_name)
end
@@ -119,6 +123,10 @@ module ActiveRecord
@mutation_tracker = @mutation_tracker.now_tracking(@attributes)
end
+ def previous_mutation_tracker
+ @previous_mutation_tracker ||= NullMutationTracker.new
+ end
+
def cache_changed_attributes
@cached_changed_attributes = changed_attributes
yield
diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activerecord/lib/active_record/attribute_mutation_tracker.rb
index e4be5c5524..168794fcb4 100644
--- a/activerecord/lib/active_record/attribute_mutation_tracker.rb
+++ b/activerecord/lib/active_record/attribute_mutation_tracker.rb
@@ -13,6 +13,14 @@ module ActiveRecord
end
end
+ def changes
+ attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
+ if changed?(attr_name)
+ result[attr_name] = [original_attributes.fetch_value(attr_name), attributes.fetch_value(attr_name)]
+ end
+ end
+ end
+
def changed?(attr_name)
attr_name = attr_name.to_s
modified?(attr_name) || changed_in_place?(attr_name)
@@ -52,4 +60,25 @@ module ActiveRecord
end
end
end
+
+ class NullMutationTracker
+ def changed_values
+ {}
+ end
+
+ def changes
+ {}
+ end
+
+ def changed?(*)
+ false
+ end
+
+ def changed_in_place?(*)
+ false
+ end
+
+ def forget_change(*)
+ end
+ end
end