diff options
| author | Ryuta Kamizono <kamipo@gmail.com> | 2019-04-09 08:28:31 +0900 | 
|---|---|---|
| committer | Ryuta Kamizono <kamipo@gmail.com> | 2019-04-11 16:30:40 +0900 | 
| commit | 6b0a9de906f2f96a847b5eb93755db0911f2615d (patch) | |
| tree | 572d4af77a85b9b804d97b77a0fc37691d249090 /activerecord/lib/active_record/attribute_methods | |
| parent | d4e2824d8d9aea6d95e78275c0107bbbc48300d9 (diff) | |
| download | rails-6b0a9de906f2f96a847b5eb93755db0911f2615d.tar.gz rails-6b0a9de906f2f96a847b5eb93755db0911f2615d.tar.bz2 rails-6b0a9de906f2f96a847b5eb93755db0911f2615d.zip  | |
PERF: 2x ~ 30x faster dirty tracking
Currently, although using both dirty tracking (ivar backed and
attributes backed) on one model is not supported (doesn't fully work at
least), both dirty tracking are being performed, that is very slow.
As long as attributes backed dirty tracking is used, ivar backed dirty
tracking should not need to be performed.
I've refactored to extract new `ForcedMutationTracker` which only tracks
`force_change` to be performed for ivar backed dirty tracking, that
makes dirty tracking on Active Record 2x ~ 30x faster.
https://gist.github.com/kamipo/971dfe0891f0fe1ec7db8ab31f016435
Before:
```
Warming up --------------------------------------
            changed?     4.467k i/100ms
             changed     5.134k i/100ms
             changes     3.023k i/100ms
  changed_attributes     4.358k i/100ms
        title_change     3.185k i/100ms
           title_was     3.381k i/100ms
Calculating -------------------------------------
            changed?     42.197k (±28.5%) i/s -    187.614k in   5.050446s
             changed     50.481k (±16.0%) i/s -    246.432k in   5.045759s
             changes     30.799k (± 7.2%) i/s -    154.173k in   5.030765s
  changed_attributes     51.530k (±14.2%) i/s -    252.764k in   5.041106s
        title_change     44.667k (± 9.0%) i/s -    222.950k in   5.040646s
           title_was     44.635k (±16.6%) i/s -    216.384k in   5.051098s
```
After:
```
Warming up --------------------------------------
            changed?    24.130k i/100ms
             changed    13.503k i/100ms
             changes     6.511k i/100ms
  changed_attributes     9.226k i/100ms
        title_change    48.221k i/100ms
           title_was    96.060k i/100ms
Calculating -------------------------------------
            changed?    245.478k (±16.1%) i/s -      1.182M in   5.015837s
             changed    157.641k (± 4.9%) i/s -    796.677k in   5.066734s
             changes     70.633k (± 5.7%) i/s -    358.105k in   5.086553s
  changed_attributes     95.155k (±13.6%) i/s -    470.526k in   5.082841s
        title_change    566.481k (± 3.5%) i/s -      2.845M in   5.028852s
           title_was      1.487M (± 3.9%) i/s -      7.493M in   5.046774s
```
Diffstat (limited to 'activerecord/lib/active_record/attribute_methods')
| -rw-r--r-- | activerecord/lib/active_record/attribute_methods/dirty.rb | 14 | 
1 files changed, 6 insertions, 8 deletions
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 45e4b8adfa..920a7b2038 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -29,9 +29,7 @@ module ActiveRecord        # <tt>reload</tt> the record and clears changed attributes.        def reload(*)          super.tap do -          @previously_changed = ActiveSupport::HashWithIndifferentAccess.new            @mutations_before_last_save = nil -          @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new            @mutations_from_database = nil          end        end @@ -51,7 +49,7 @@ module ActiveRecord        # +to+ When passed, this method will return false unless the value was        # changed to the given value        def saved_change_to_attribute?(attr_name, **options) -        mutations_before_last_save.changed?(attr_name, **options) +        mutations_before_last_save.changed?(attr_name.to_s, options)        end        # Returns the change to an attribute during the last save. If the @@ -63,7 +61,7 @@ module ActiveRecord        # invoked as +saved_change_to_name+ instead of        # <tt>saved_change_to_attribute("name")</tt>.        def saved_change_to_attribute(attr_name) -        mutations_before_last_save.change_to_attribute(attr_name) +        mutations_before_last_save.change_to_attribute(attr_name.to_s)        end        # Returns the original value of an attribute before the last save. @@ -73,7 +71,7 @@ module ActiveRecord        # invoked as +name_before_last_save+ instead of        # <tt>attribute_before_last_save("name")</tt>.        def attribute_before_last_save(attr_name) -        mutations_before_last_save.original_value(attr_name) +        mutations_before_last_save.original_value(attr_name.to_s)        end        # Did the last call to +save+ have any changes to change? @@ -101,7 +99,7 @@ module ActiveRecord        # +to+ When passed, this method will return false unless the value will be        # changed to the given value        def will_save_change_to_attribute?(attr_name, **options) -        mutations_from_database.changed?(attr_name, **options) +        mutations_from_database.changed?(attr_name.to_s, options)        end        # Returns the change to an attribute that will be persisted during the @@ -115,7 +113,7 @@ module ActiveRecord        # If the attribute will change, the result will be an array containing the        # original value and the new value about to be saved.        def attribute_change_to_be_saved(attr_name) -        mutations_from_database.change_to_attribute(attr_name) +        mutations_from_database.change_to_attribute(attr_name.to_s)        end        # Returns the value of an attribute in the database, as opposed to the @@ -127,7 +125,7 @@ module ActiveRecord        # saved. It can be invoked as +name_in_database+ instead of        # <tt>attribute_in_database("name")</tt>.        def attribute_in_database(attr_name) -        mutations_from_database.original_value(attr_name) +        mutations_from_database.original_value(attr_name.to_s)        end        # Will the next call to +save+ have any changes to persist?  | 
