mirror of https://github.com/rails/rails
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 ```
This commit is contained in:
parent
1c8be9c67a
commit
7cfc4ee676
|
@ -42,20 +42,20 @@ class Time
|
|||
|
||||
# Layers additional behavior on Time.at so that ActiveSupport::TimeWithZone and DateTime
|
||||
# instances can be used when called with a single argument
|
||||
def at_with_coercion(*args, **kwargs)
|
||||
return at_without_coercion(*args, **kwargs) if args.size != 1 || !kwargs.empty?
|
||||
|
||||
# Time.at can be called with a time or numerical value
|
||||
time_or_number = args.first
|
||||
|
||||
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
|
||||
def 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)
|
||||
at_without_coercion(time_or_number, *args)
|
||||
end
|
||||
end
|
||||
ruby2_keywords :at_with_coercion
|
||||
alias_method :at_without_coercion, :at
|
||||
alias_method :at, :at_with_coercion
|
||||
|
||||
|
|
Loading…
Reference in New Issue