1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Improve ActiveSupport::Duration performance about 25% ~ 50%

`ActiveSupport::Duration` expects `parts` as a Hash in the internal, but
sometimes that is passed as nested array.

(e.g. `parts.map { |type, number| [type, number * other] }`)

By avoiding the extra array allocation, it makes
`ActiveSupport::Duration` performance faster about 25% ~ 50%.

```ruby
Benchmark.ips do |x|
  x.report("4.seconds + 2.seconds") { 4.seconds + 2.seconds }
  x.report("4.seconds - 2.seconds") { 4.seconds - 2.seconds }
  x.report("4.seconds * 2") { 4.seconds * 2 }
  x.report("4.seconds / 2") { 4.seconds / 2 }
end
```

Before (e36d4a03):

```
Warming up --------------------------------------
4.seconds + 2.seconds   27.515k i/100ms
4.seconds - 2.seconds   20.783k i/100ms
       4.seconds * 2    40.286k i/100ms
       4.seconds / 2    41.717k i/100ms
Calculating -------------------------------------
4.seconds + 2.seconds   299.133k (±13.4%) i/s -      1.486M in   5.053953s
4.seconds - 2.seconds   222.476k (± 9.8%) i/s -      1.122M in   5.088532s
       4.seconds * 2    479.650k (±10.5%) i/s -      2.377M in   5.008644s
       4.seconds / 2    473.768k (±11.3%) i/s -      2.378M in   5.082413s
```

After (this change):

```
Warming up --------------------------------------
4.seconds + 2.seconds   35.856k i/100ms
4.seconds - 2.seconds   28.657k i/100ms
       4.seconds * 2    51.744k i/100ms
       4.seconds / 2    51.417k i/100ms
Calculating -------------------------------------
4.seconds + 2.seconds   445.972k (±21.6%) i/s -      2.080M in   5.018570s
4.seconds - 2.seconds   317.972k (±18.2%) i/s -      1.519M in   5.006564s
       4.seconds * 2    608.695k (±12.8%) i/s -      3.001M in   5.014321s
       4.seconds / 2    611.488k (±12.0%) i/s -      3.034M in   5.035211s
```
This commit is contained in:
Ryuta Kamizono 2019-12-20 17:06:03 +09:00
parent e36d4a0381
commit fa0587b56d

View file

@ -39,7 +39,7 @@ module ActiveSupport
def +(other)
if Duration === other
seconds = value + other.parts[:seconds]
seconds = value + other.parts.fetch(:seconds, 0)
new_parts = other.parts.merge(seconds: seconds)
new_value = value + other.value
@ -51,8 +51,8 @@ module ActiveSupport
def -(other)
if Duration === other
seconds = value - other.parts[:seconds]
new_parts = other.parts.map { |part, other_value| [part, -other_value] }.to_h
seconds = value - other.parts.fetch(:seconds, 0)
new_parts = other.parts.transform_values(&:-@)
new_parts = new_parts.merge(seconds: seconds)
new_value = value - other.value
@ -64,7 +64,7 @@ module ActiveSupport
def *(other)
if Duration === other
new_parts = other.parts.map { |part, other_value| [part, value * other_value] }.to_h
new_parts = other.parts.transform_values { |other_value| value * other_value }
new_value = value * other.value
Duration.new(new_value, new_parts)
@ -147,31 +147,31 @@ module ActiveSupport
end
def seconds(value) #:nodoc:
new(value, [[:seconds, value]])
new(value, seconds: value)
end
def minutes(value) #:nodoc:
new(value * SECONDS_PER_MINUTE, [[:minutes, value]])
new(value * SECONDS_PER_MINUTE, minutes: value)
end
def hours(value) #:nodoc:
new(value * SECONDS_PER_HOUR, [[:hours, value]])
new(value * SECONDS_PER_HOUR, hours: value)
end
def days(value) #:nodoc:
new(value * SECONDS_PER_DAY, [[:days, value]])
new(value * SECONDS_PER_DAY, days: value)
end
def weeks(value) #:nodoc:
new(value * SECONDS_PER_WEEK, [[:weeks, value]])
new(value * SECONDS_PER_WEEK, weeks: value)
end
def months(value) #:nodoc:
new(value * SECONDS_PER_MONTH, [[:months, value]])
new(value * SECONDS_PER_MONTH, months: value)
end
def years(value) #:nodoc:
new(value * SECONDS_PER_YEAR, [[:years, value]])
new(value * SECONDS_PER_YEAR, years: value)
end
# Creates a new Duration from a seconds value that is converted
@ -210,8 +210,7 @@ module ActiveSupport
end
def initialize(value, parts) #:nodoc:
@value, @parts = value, parts.to_h
@parts.default = 0
@value, @parts = value, parts
@parts.reject! { |k, v| v.zero? } unless value == 0
end
@ -240,13 +239,12 @@ module ActiveSupport
# are treated as seconds.
def +(other)
if Duration === other
parts = @parts.dup
other.parts.each do |(key, value)|
parts[key] += value
parts = @parts.merge(other.parts) do |_key, value, other_value|
value + other_value
end
Duration.new(value + other.value, parts)
else
seconds = @parts[:seconds] + other
seconds = @parts.fetch(:seconds, 0) + other
Duration.new(value + other, @parts.merge(seconds: seconds))
end
end
@ -260,9 +258,9 @@ module ActiveSupport
# Multiplies this Duration by a Numeric and returns a new Duration.
def *(other)
if Scalar === other || Duration === other
Duration.new(value * other.value, parts.map { |type, number| [type, number * other.value] })
Duration.new(value * other.value, parts.transform_values { |number| number * other.value })
elsif Numeric === other
Duration.new(value * other, parts.map { |type, number| [type, number * other] })
Duration.new(value * other, parts.transform_values { |number| number * other })
else
raise_type_error(other)
end
@ -271,11 +269,11 @@ module ActiveSupport
# Divides this Duration by a Numeric and returns a new Duration.
def /(other)
if Scalar === other
Duration.new(value / other.value, parts.map { |type, number| [type, number / other.value] })
Duration.new(value / other.value, parts.transform_values { |number| number / other.value })
elsif Duration === other
value / other.value
elsif Numeric === other
Duration.new(value / other, parts.map { |type, number| [type, number / other] })
Duration.new(value / other, parts.transform_values { |number| number / other })
else
raise_type_error(other)
end
@ -294,7 +292,7 @@ module ActiveSupport
end
def -@ #:nodoc:
Duration.new(-value, parts.map { |type, number| [type, -number] })
Duration.new(-value, parts.transform_values(&:-@))
end
def is_a?(klass) #:nodoc: