rails/activesupport
Jean Boussier 7cfc4ee676 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
```
2023-12-05 12:42:40 +01:00
..
bin Remove `AS::Multibyte`'s unicode table 2018-02-20 03:58:22 +09:00
lib Optimize `Time.at_with_coercion` 2023-12-05 12:42:40 +01:00
test Fix Time.now/DateTime.now/Date.today to return results in a system timezone after #travel_to 2023-12-02 10:05:31 +01:00
.gitignore
CHANGELOG.md Fix Time.now/DateTime.now/Date.today to return results in a system timezone after #travel_to 2023-12-02 10:05:31 +01:00
MIT-LICENSE Remove Copyright years (#47467) 2023-02-23 11:38:16 +01:00
README.rdoc 🔗 Remove RDoc auto-link from Rails module everywhere 2023-06-23 10:49:30 +09:00
Rakefile Stop testing hiredis 2022-08-22 09:01:27 +02:00
activesupport.gemspec Automatically eager load TZInfo 2023-11-09 18:44:27 +01:00

README.rdoc

= Active Support -- Utility classes and Ruby extensions from \Rails

Active Support is a collection of utility classes and standard library
extensions that were found useful for the \Rails framework. These additions
reside in this package so they can be loaded as needed in Ruby projects
outside of \Rails.

You can read more about the extensions in the {Active Support Core Extensions}[https://guides.rubyonrails.org/active_support_core_extensions.html] guide.

== Download and installation

The latest version of Active Support can be installed with RubyGems:

  $ gem install activesupport

Source code can be downloaded as part of the \Rails project on GitHub:

* https://github.com/rails/rails/tree/main/activesupport


== License

Active Support is released under the MIT license:

* https://opensource.org/licenses/MIT


== Support

API documentation is at:

* https://api.rubyonrails.org

Bug reports for the Ruby on \Rails project can be filed here:

* https://github.com/rails/rails/issues

Feature requests should be discussed on the rails-core mailing list here:

* https://discuss.rubyonrails.org/c/rubyonrails-core