Skip to content

Commit 7cfc4ee

Browse files
committed
Optimize Time.at_with_coercion
By using `ruby2_keyword` style delegation we we can avoid a few allocations and some extra checks. ``` $ ruby --yjit /tmp/bench-as-time-at.rb ruby 3.2.2 (2023-03-30 revision e51014f9c0) +YJIT [arm64-darwin22] === Complex call ==== Warming up -------------------------------------- Time.without 320.514k i/100ms Time.with 68.433k i/100ms Time.opt_with 167.532k i/100ms Calculating ------------------------------------- Time.without 3.781M (± 4.8%) i/s - 18.910M in 5.014574s Time.with 1.586M (± 3.5%) i/s - 7.938M in 5.010525s Time.opt_with 2.003M (± 2.4%) i/s - 10.052M in 5.021309s Comparison: Time.without: 3781330.9 i/s Time.opt_with: 2003025.9 i/s - 1.89x slower Time.with: 1586289.9 i/s - 2.38x slower Time.without: 2.003 alloc/iter Time.with: 9.002 alloc/iter Time.opt_with: 7.002 alloc/iter === Simple call ==== Warming up -------------------------------------- Time.without 749.097k i/100ms Time.with 342.855k i/100ms Time.opt_with 416.063k i/100ms Calculating ------------------------------------- Time.without 9.289M (± 3.4%) i/s - 46.444M in 5.005361s Time.with 3.601M (± 2.1%) i/s - 18.171M in 5.048794s Time.opt_with 4.373M (± 8.1%) i/s - 22.051M in 5.084967s Comparison: Time.without: 9289271.2 i/s Time.opt_with: 4373226.2 i/s - 2.12x slower Time.with: 3600733.6 i/s - 2.58x slower Time.without: 1.002 alloc/iter Time.with: 3.001 alloc/iter Time.opt_with: 3.002 alloc/iter ``` ```ruby require 'bundler/inline' gemfile do source 'https://rubygems.org' gem 'activesupport', require: 'active_support/all', github: 'rails/rails' gem 'benchmark-ips' end class Time class << self def opt_at_with_coercion(time_or_number, *args) if args.empty? if time_or_number.is_a?(ActiveSupport::TimeWithZone) at_without_coercion(time_or_number.to_r).getlocal elsif time_or_number.is_a?(DateTime) at_without_coercion(time_or_number.to_f).getlocal else at_without_coercion(time_or_number) end else at_without_coercion(time_or_number, *args) end end ruby2_keywords :opt_at_with_coercion end end puts RUBY_DESCRIPTION puts "=== Complex call ====" Benchmark.ips do |x| x.report("Time.without") do ::Time.at_without_coercion(223423423, 32423423, :nanosecond, in: "UTC") end x.report("Time.with") do ::Time.at_with_coercion(223423423, 32423423, :nanosecond, in: "UTC") end x.report("Time.opt_with") do ::Time.opt_at_with_coercion(223423423, 32423423, :nanosecond, in: "UTC") end x.compare!(order: :baseline) end def measure_allocs(title, iterations: 1_000) before = GC.stat(:total_allocated_objects) iterations.times do yield end allocs = GC.stat(:total_allocated_objects) - before puts "#{title}: #{allocs.to_f / iterations} alloc/iter" end measure_allocs("Time.without") do ::Time.at_without_coercion(223423423, 32423423, :nanosecond, in: "UTC") end measure_allocs("Time.with") do ::Time.at_with_coercion(223423423, 32423423, :nanosecond, in: "UTC") end measure_allocs("Time.opt_with") do ::Time.opt_at_with_coercion(223423423, 32423423, :nanosecond, in: "UTC") end puts "=== Simple call ====" Benchmark.ips do |x| x.report("Time.without") do ::Time.at_without_coercion(223423423) end x.report("Time.with") do ::Time.at_with_coercion(223423423) end x.report("Time.opt_with") do ::Time.opt_at_with_coercion(223423423) end x.compare!(order: :baseline) end def measure_allocs(title, iterations: 1_000) before = GC.stat(:total_allocated_objects) iterations.times do yield end allocs = GC.stat(:total_allocated_objects) - before puts "#{title}: #{allocs.to_f / iterations} alloc/iter" end measure_allocs("Time.without") do ::Time.at_without_coercion(223423423) end measure_allocs("Time.with") do ::Time.at_with_coercion(223423423) end measure_allocs("Time.opt_with") do ::Time.opt_at_with_coercion(223423423, 32423423) end ```
1 parent 1c8be9c commit 7cfc4ee

File tree

1 file changed

+11
-11
lines changed

1 file changed

+11
-11
lines changed

activesupport/lib/active_support/core_ext/time/calculations.rb

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,20 @@ def current
4242

4343
# Layers additional behavior on Time.at so that ActiveSupport::TimeWithZone and DateTime
4444
# instances can be used when called with a single argument
45-
def at_with_coercion(*args, **kwargs)
46-
return at_without_coercion(*args, **kwargs) if args.size != 1 || !kwargs.empty?
47-
48-
# Time.at can be called with a time or numerical value
49-
time_or_number = args.first
50-
51-
if time_or_number.is_a?(ActiveSupport::TimeWithZone)
52-
at_without_coercion(time_or_number.to_r).getlocal
53-
elsif time_or_number.is_a?(DateTime)
54-
at_without_coercion(time_or_number.to_f).getlocal
45+
def at_with_coercion(time_or_number, *args)
46+
if args.empty?
47+
if time_or_number.is_a?(ActiveSupport::TimeWithZone)
48+
at_without_coercion(time_or_number.to_r).getlocal
49+
elsif time_or_number.is_a?(DateTime)
50+
at_without_coercion(time_or_number.to_f).getlocal
51+
else
52+
at_without_coercion(time_or_number)
53+
end
5554
else
56-
at_without_coercion(time_or_number)
55+
at_without_coercion(time_or_number, *args)
5756
end
5857
end
58+
ruby2_keywords :at_with_coercion
5959
alias_method :at_without_coercion, :at
6060
alias_method :at, :at_with_coercion
6161

0 commit comments

Comments
 (0)