diff options
author | Sean Griffin <sean@seantheprogrammer.com> | 2015-09-24 14:03:27 -0600 |
---|---|---|
committer | Sean Griffin <sean@seantheprogrammer.com> | 2015-09-24 14:06:59 -0600 |
commit | 136fc65c9b8b66e1fb56f3a17f0d1fddff9b4bd0 (patch) | |
tree | 88df6c3943bc23df8d5acf748cb8c02f9221f376 /activerecord | |
parent | 8a811c83aa386e4ad26553c913d1a450d8a98d5f (diff) | |
download | rails-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')
-rw-r--r-- | activerecord/lib/active_record/attribute_methods/dirty.rb | 12 | ||||
-rw-r--r-- | activerecord/lib/active_record/attribute_mutation_tracker.rb | 29 |
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 |