Skip to content
  • Jean Boussier's avatar
    d36eb239
    Specialize various `#present?` implementations · d36eb239
    Jean Boussier authored
    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
    ```
    d36eb239
    Specialize various `#present?` implementations
    Jean Boussier authored
    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
    ```
Loading