-
Jonathan Hefner authored
Most model attribute types try to cast a given value before serializing it. This allows uncast values to be passed to finder methods and still be serialized appropriately. However, when persisting a model, this cast is unnecessary because the value will already have been cast by `ActiveModel::Attribute#value`. To eliminate the overhead of a 2nd cast, this commit introduces a `ActiveModel::Type::SerializeCastValue` module. Types can include this module, and their `serialize_cast_value` method will be called instead of `serialize` when serializing an already-cast value. To preserve existing behavior of any user types that subclass Rails' types, `serialize_after_cast` will only be called if the type itself (not a superclass) includes `ActiveModel::Type::SerializeCastValue`. This also applies to type decorators implemented via `DelegateClass`. Benchmark script: ```ruby require "active_model" require "benchmark/ips" class ActiveModel::Attribute alias baseline_value_for_database value_for_database end VALUES = { my_big_integer: "123456", my_boolean: "true", my_date: "1999-12-31", my_datetime: "1999-12-31 12:34:56 UTC", my_decimal: "123.456", my_float: "123.456", my_immutable_string: "abcdef", my_integer: "123456", my_string: "abcdef", my_time: "1999-12-31T12:34:56.789-10:00", } TYPES = VALUES.to_h { |name, value| [name, name.to_s.delete_prefix("my_").to_sym] } class MyModel include ActiveModel::API include ActiveModel::Attributes TYPES.each do |name, type| attribute name, type end end TYPES.each do |name, type| $attribute_set ||= MyModel.new(VALUES).instance_variable_get(:@attributes) attribute = $attribute_set[name.to_s] puts "=" * 72 Benchmark.ips do |x| x.report("#{type} before") { attribute.baseline_value_for_database } x.report("#{type} after") { attribute.value_for_database } x.compare! end end ``` Benchmark results: ``` ======================================================================== Warming up -------------------------------------- big_integer before 100.417k i/100ms big_integer after 260.375k i/100ms Calculating ------------------------------------- big_integer before 1.005M (± 1.0%) i/s - 5.121M in 5.096498s big_integer after 2.630M (± 1.0%) i/s - 13.279M in 5.050387s Comparison: big_integer after: 2629583.6 i/s big_integer before: 1004961.2 i/s - 2.62x (± 0.00) slower ======================================================================== Warming up -------------------------------------- boolean before 230.663k i/100ms boolean after 299.262k i/100ms Calculating ------------------------------------- boolean before 2.313M (± 0.7%) i/s - 11.764M in 5.085925s boolean after 3.037M (± 0.6%) i/s - 15.262M in 5.026280s Comparison: boolean after: 3036640.8 i/s boolean before: 2313127.8 i/s - 1.31x (± 0.00) slower ======================================================================== Warming up -------------------------------------- date before 148.821k i/100ms date after 298.939k i/100ms Calculating ------------------------------------- date before 1.486M (± 0.6%) i/s - 7.441M in 5.006091s date after 2.963M (± 0.8%) i/s - 14.947M in 5.045651s Comparison: date after: 2962535.3 i/s date before: 1486459.4 i/s - 1.99x (± 0.00) slower ======================================================================== Warming up -------------------------------------- datetime before 92.818k i/100ms datetime after 136.710k i/100ms Calculating ------------------------------------- datetime before 920.236k (± 0.6%) i/s - 4.641M in 5.043355s datetime after 1.366M (± 0.8%) i/s - 6.836M in 5.003307s Comparison: datetime after: 1366294.1 i/s datetime before: 920236.1 i/s - 1.48x (± 0.00) slower ======================================================================== Warming up -------------------------------------- decimal before 50.194k i/100ms decimal after 298.674k i/100ms Calculating ------------------------------------- decimal before 494.141k (± 1.4%) i/s - 2.510M in 5.079995s decimal after 3.015M (± 1.0%) i/s - 15.232M in 5.052929s Comparison: decimal after: 3014901.3 i/s decimal before: 494141.2 i/s - 6.10x (± 0.00) slower ======================================================================== Warming up -------------------------------------- float before 217.547k i/100ms float after 298.106k i/100ms Calculating ------------------------------------- float before 2.157M (± 0.8%) i/s - 10.877M in 5.043292s float after 2.991M (± 0.6%) i/s - 15.203M in 5.082806s Comparison: float after: 2991262.8 i/s float before: 2156940.2 i/s - 1.39x (± 0.00) slower ======================================================================== Warming up -------------------------------------- immutable_string before 163.287k i/100ms immutable_string after 298.245k i/100ms Calculating ------------------------------------- immutable_string before 1.652M (± 0.7%) i/s - 8.328M in 5.040855s immutable_string after 3.022M (± 0.9%) i/s - 15.210M in 5.033151s Comparison: immutable_string after: 3022313.3 i/s immutable_string before: 1652121.7 i/s - 1.83x (± 0.00) slower ======================================================================== Warming up -------------------------------------- integer before 115.383k i/100ms integer after 159.702k i/100ms Calculating ------------------------------------- integer before 1.132M (± 0.8%) i/s - 5.769M in 5.095041s integer after 1.641M (± 0.5%) i/s - 8.305M in 5.061893s Comparison: integer after: 1640635.8 i/s integer before: 1132381.5 i/s - 1.45x (± 0.00) slower ======================================================================== Warming up -------------------------------------- string before 163.061k i/100ms string after 299.885k i/100ms Calculating ------------------------------------- string before 1.659M (± 0.7%) i/s - 8.316M in 5.012609s string after 2.999M (± 0.6%) i/s - 15.294M in 5.100008s Comparison: string after: 2998956.0 i/s string before: 1659115.6 i/s - 1.81x (± 0.00) slower ======================================================================== Warming up -------------------------------------- time before 98.250k i/100ms time after 133.463k i/100ms Calculating ------------------------------------- time before 987.771k (± 0.7%) i/s - 5.011M in 5.073023s time after 1.330M (± 0.5%) i/s - 6.673M in 5.016573s Comparison: time after: 1330253.9 i/s time before: 987771.0 i/s - 1.35x (± 0.00) slower ```
Jonathan Hefner authoredMost model attribute types try to cast a given value before serializing it. This allows uncast values to be passed to finder methods and still be serialized appropriately. However, when persisting a model, this cast is unnecessary because the value will already have been cast by `ActiveModel::Attribute#value`. To eliminate the overhead of a 2nd cast, this commit introduces a `ActiveModel::Type::SerializeCastValue` module. Types can include this module, and their `serialize_cast_value` method will be called instead of `serialize` when serializing an already-cast value. To preserve existing behavior of any user types that subclass Rails' types, `serialize_after_cast` will only be called if the type itself (not a superclass) includes `ActiveModel::Type::SerializeCastValue`. This also applies to type decorators implemented via `DelegateClass`. Benchmark script: ```ruby require "active_model" require "benchmark/ips" class ActiveModel::Attribute alias baseline_value_for_database value_for_database end VALUES = { my_big_integer: "123456", my_boolean: "true", my_date: "1999-12-31", my_datetime: "1999-12-31 12:34:56 UTC", my_decimal: "123.456", my_float: "123.456", my_immutable_string: "abcdef", my_integer: "123456", my_string: "abcdef", my_time: "1999-12-31T12:34:56.789-10:00", } TYPES = VALUES.to_h { |name, value| [name, name.to_s.delete_prefix("my_").to_sym] } class MyModel include ActiveModel::API include ActiveModel::Attributes TYPES.each do |name, type| attribute name, type end end TYPES.each do |name, type| $attribute_set ||= MyModel.new(VALUES).instance_variable_get(:@attributes) attribute = $attribute_set[name.to_s] puts "=" * 72 Benchmark.ips do |x| x.report("#{type} before") { attribute.baseline_value_for_database } x.report("#{type} after") { attribute.value_for_database } x.compare! end end ``` Benchmark results: ``` ======================================================================== Warming up -------------------------------------- big_integer before 100.417k i/100ms big_integer after 260.375k i/100ms Calculating ------------------------------------- big_integer before 1.005M (± 1.0%) i/s - 5.121M in 5.096498s big_integer after 2.630M (± 1.0%) i/s - 13.279M in 5.050387s Comparison: big_integer after: 2629583.6 i/s big_integer before: 1004961.2 i/s - 2.62x (± 0.00) slower ======================================================================== Warming up -------------------------------------- boolean before 230.663k i/100ms boolean after 299.262k i/100ms Calculating ------------------------------------- boolean before 2.313M (± 0.7%) i/s - 11.764M in 5.085925s boolean after 3.037M (± 0.6%) i/s - 15.262M in 5.026280s Comparison: boolean after: 3036640.8 i/s boolean before: 2313127.8 i/s - 1.31x (± 0.00) slower ======================================================================== Warming up -------------------------------------- date before 148.821k i/100ms date after 298.939k i/100ms Calculating ------------------------------------- date before 1.486M (± 0.6%) i/s - 7.441M in 5.006091s date after 2.963M (± 0.8%) i/s - 14.947M in 5.045651s Comparison: date after: 2962535.3 i/s date before: 1486459.4 i/s - 1.99x (± 0.00) slower ======================================================================== Warming up -------------------------------------- datetime before 92.818k i/100ms datetime after 136.710k i/100ms Calculating ------------------------------------- datetime before 920.236k (± 0.6%) i/s - 4.641M in 5.043355s datetime after 1.366M (± 0.8%) i/s - 6.836M in 5.003307s Comparison: datetime after: 1366294.1 i/s datetime before: 920236.1 i/s - 1.48x (± 0.00) slower ======================================================================== Warming up -------------------------------------- decimal before 50.194k i/100ms decimal after 298.674k i/100ms Calculating ------------------------------------- decimal before 494.141k (± 1.4%) i/s - 2.510M in 5.079995s decimal after 3.015M (± 1.0%) i/s - 15.232M in 5.052929s Comparison: decimal after: 3014901.3 i/s decimal before: 494141.2 i/s - 6.10x (± 0.00) slower ======================================================================== Warming up -------------------------------------- float before 217.547k i/100ms float after 298.106k i/100ms Calculating ------------------------------------- float before 2.157M (± 0.8%) i/s - 10.877M in 5.043292s float after 2.991M (± 0.6%) i/s - 15.203M in 5.082806s Comparison: float after: 2991262.8 i/s float before: 2156940.2 i/s - 1.39x (± 0.00) slower ======================================================================== Warming up -------------------------------------- immutable_string before 163.287k i/100ms immutable_string after 298.245k i/100ms Calculating ------------------------------------- immutable_string before 1.652M (± 0.7%) i/s - 8.328M in 5.040855s immutable_string after 3.022M (± 0.9%) i/s - 15.210M in 5.033151s Comparison: immutable_string after: 3022313.3 i/s immutable_string before: 1652121.7 i/s - 1.83x (± 0.00) slower ======================================================================== Warming up -------------------------------------- integer before 115.383k i/100ms integer after 159.702k i/100ms Calculating ------------------------------------- integer before 1.132M (± 0.8%) i/s - 5.769M in 5.095041s integer after 1.641M (± 0.5%) i/s - 8.305M in 5.061893s Comparison: integer after: 1640635.8 i/s integer before: 1132381.5 i/s - 1.45x (± 0.00) slower ======================================================================== Warming up -------------------------------------- string before 163.061k i/100ms string after 299.885k i/100ms Calculating ------------------------------------- string before 1.659M (± 0.7%) i/s - 8.316M in 5.012609s string after 2.999M (± 0.6%) i/s - 15.294M in 5.100008s Comparison: string after: 2998956.0 i/s string before: 1659115.6 i/s - 1.81x (± 0.00) slower ======================================================================== Warming up -------------------------------------- time before 98.250k i/100ms time after 133.463k i/100ms Calculating ------------------------------------- time before 987.771k (± 0.7%) i/s - 5.011M in 5.073023s time after 1.330M (± 0.5%) i/s - 6.673M in 5.016573s Comparison: time after: 1330253.9 i/s time before: 987771.0 i/s - 1.35x (± 0.00) slower ```
Loading