Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specialize various #present? implementations #49909

Merged
merged 1 commit into from Nov 4, 2023

Conversation

byroot
Copy link
Member

@byroot byroot commented Nov 3, 2023

Because #present? always resolve to Object#present?, it's an extremely polymorphic method, and inline cache hits are low.

In addition, it requires an extra call to self.blank? which is an overhead.

By specializing present? on common types, we avoid both of these slow-downs:


ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin22]
Warming up --------------------------------------
            present?   198.028k i/100ms
        opt_present?   565.521k i/100ms
Calculating -------------------------------------
            present?      2.087M (± 8.8%) i/s -     10.297M in   5.028398s
        opt_present?      5.584M (± 8.6%) i/s -     27.711M in   5.023852s

Comparison:
            present?:  2086621.6 i/s
        opt_present?:  5584373.5 i/s - 2.68x  faster
ruby 3.2.2 (2023-03-30 revision e51014f9c0) +YJIT [arm64-darwin22]
Warming up --------------------------------------
            present?   819.792k i/100ms
        opt_present?     1.047M i/100ms
Calculating -------------------------------------
            present?     12.192M (± 8.8%) i/s -     60.665M in   5.050622s
        opt_present?     16.540M (± 8.2%) i/s -     82.676M in   5.059029s

Comparison:
            present?: 12192047.5 i/s
        opt_present?: 16539689.6 i/s - 1.36x  faster
require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'benchmark-ips'
  gem 'activesupport'
end

require 'active_support/all'

class Object
  def opt_present?
    respond_to?(:empty?) ? !empty? : !!self
  end
end

class NilClass
  def opt_present?
    false
  end
end

class FalseClass
  def opt_present?
    false
  end
end

class TrueClass
  def opt_present?
    true
  end
end

class Array
  def opt_present?
    !empty?
  end
end

class Hash
  def opt_present?
    !empty?
  end
end

class Symbol
  def opt_present?
    !empty?
  end
end

class String
  def opt_present?
    !blank?
  end
end

class Numeric # :nodoc:
  def opt_present?
    true
  end
end

class Time # :nodoc:
  def opt_present?
    true
  end
end

array = []
hash = {}
time = Time.now

puts RUBY_DESCRIPTION
Benchmark.ips do |x|
  x.report("present?") do
    true.present?
    false.present?
    1.present?
    1.0.present?
    array.present?
    hash.present?
    :foo.present?
    time.present?
  end

  x.report("opt_present?") do
    true.opt_present?
    false.opt_present?
    1.opt_present?
    1.0.opt_present?
    array.opt_present?
    hash.opt_present?
    :foo.opt_present?
    time.opt_present?
  end

  x.compare!(order: :baseline)
end

Because `#present?` always resolve to `Object#present?`, it's
an extremely polymorphic method, and inline cache hits are low.

In addition, it requires an extra call to `self.blank?` which is
an overhead.

By specializing `present?` on common types, we avoid both of these
slow-downs:

```

ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin22]
Warming up --------------------------------------
            present?   198.028k i/100ms
        opt_present?   565.521k i/100ms
Calculating -------------------------------------
            present?      2.087M (± 8.8%) i/s -     10.297M in   5.028398s
        opt_present?      5.584M (± 8.6%) i/s -     27.711M in   5.023852s

Comparison:
            present?:  2086621.6 i/s
        opt_present?:  5584373.5 i/s - 2.68x  faster
```

```
ruby 3.2.2 (2023-03-30 revision e51014f9c0) +YJIT [arm64-darwin22]
Warming up --------------------------------------
            present?   819.792k i/100ms
        opt_present?     1.047M i/100ms
Calculating -------------------------------------
            present?     12.192M (± 8.8%) i/s -     60.665M in   5.050622s
        opt_present?     16.540M (± 8.2%) i/s -     82.676M in   5.059029s

Comparison:
            present?: 12192047.5 i/s
        opt_present?: 16539689.6 i/s - 1.36x  faster
```

```ruby

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'benchmark-ips'
  gem 'activesupport'
end

require 'active_support/all'

class Object
  def opt_present?
    respond_to?(:empty?) ? !empty? : !!self
  end
end

class NilClass
  def opt_present?
    false
  end
end

class FalseClass
  def opt_present?
    false
  end
end

class TrueClass
  def opt_present?
    true
  end
end

class Array
  def opt_present?
    !empty?
  end
end

class Hash
  def opt_present?
    !empty?
  end
end

class Symbol
  def opt_present?
    !empty?
  end
end

class String
  def opt_present?
    !blank?
  end
end

class Numeric # :nodoc:
  def opt_present?
    true
  end
end

class Time # :nodoc:
  def opt_present?
    true
  end
end

array = []
hash = {}
time = Time.now

puts RUBY_DESCRIPTION
Benchmark.ips do |x|
  x.report("present?") do
    true.present?
    false.present?
    1.present?
    1.0.present?
    array.present?
    hash.present?
    :foo.present?
    time.present?
  end

  x.report("opt_present?") do
    true.opt_present?
    false.opt_present?
    1.opt_present?
    1.0.opt_present?
    array.opt_present?
    hash.opt_present?
    :foo.opt_present?
    time.opt_present?
  end

  x.compare!(order: :baseline)
end
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants