Skip to content

Commit d39b0f2

Browse files
committed
Reduce allocations in AS::Duration#{since,ago}
Using Enumerable#inject on a Hash forces an allocation of an array each time Hash#each is called on current CRuby versions. Use Hash#each directly. Not as elegant, for sure, but it also doesn't require understanding how inject/reduce works. Casual benchmark: ``` require 'active_support/all' require 'benchmark' p a = ActiveSupport::Duration.build(2716147) p a.parts time = Time.current pre_alloc = GC.stat(:total_allocated_objects) puts(Benchmark.measure do 300_000.times do a.since(time) end end) post_alloc = GC.stat(:total_allocated_objects) puts "alloced=#{post_alloc - pre_alloc}" ``` ```diff diff --git a/before b/after --- a/before +++ b/after @@ -1,4 +1,4 @@ 1 month, 1 day, and 1 second {:months=>1, :days=>1, :seconds=>1} - 2.311823 0.032300 2.344123 ( 2.439997) -alloced=14101243 + 2.223739 0.038590 2.262329 ( 2.316780) +alloced=12601105 ``` So ~5% speed improvement, ~10% fewer objects. I found this while profiling allocations in the [lobsters benchmark from yjit-bench][1]. [1]: https://github.com/Shopify/yjit-bench/tree/main/benchmarks/lobsters
1 parent f8e422a commit d39b0f2

File tree

1 file changed

+14
-10
lines changed

1 file changed

+14
-10
lines changed

activesupport/lib/active_support/duration.rb

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -491,17 +491,21 @@ def sum(sign, time = ::Time.current)
491491
if @parts.empty?
492492
time.since(sign * value)
493493
else
494-
@parts.inject(time) do |t, (type, number)|
495-
if type == :seconds
496-
t.since(sign * number)
497-
elsif type == :minutes
498-
t.since(sign * number * 60)
499-
elsif type == :hours
500-
t.since(sign * number * 3600)
501-
else
502-
t.advance(type => sign * number)
503-
end
494+
@parts.each do |type, number|
495+
t = time
496+
time =
497+
if type == :seconds
498+
t.since(sign * number)
499+
elsif type == :minutes
500+
t.since(sign * number * 60)
501+
elsif type == :hours
502+
t.since(sign * number * 3600)
503+
else
504+
t.advance(type => sign * number)
505+
end
504506
end
507+
508+
time
505509
end
506510
end
507511

0 commit comments

Comments
 (0)