Skip to content

bkuhlmann/benchmarks

Repository files navigation

Benchmarks

Features

  • Uses Benchmark IPS to calculate CPU/speed results.

  • Each script is independently executable.

Requirements

Setup

To install, run:

git clone https://github.com/bkuhlmann/benchmarks.git
cd benchmarks
git checkout 4.3.0
bin/setup

Usage

All benchmark scripts are found within the scripts folder and you can run any benchmark using a relative or absolute file path. Example:

scripts/strings/split

The following is a list of all benchmarks (source + results). Again, you run these locally or study the results provided instead.

scripts/arrays/concatenation

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

a = %w[one two three]
b = %w[four five six]

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report "#+" do
    a + b
  end

  benchmark.report "#+=" do
    duplicate = a.dup
    duplicate += b
  end

  benchmark.report "#concat" do
    a.dup.concat b
  end

  benchmark.report "#|" do
    a | b
  end

  benchmark.report "#<< + #flatten" do
    (a.dup << b).flatten
  end

  benchmark.report "splat + #flatten" do
    [a, *b].flatten
  end

  benchmark.report "multi-splat" do
    [*a, *b]
  end

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
                  #+     1.109M i/100ms
                 #+=   642.208k i/100ms
             #concat   640.592k i/100ms
                  #|   462.891k i/100ms
      #<< + #flatten   190.264k i/100ms
    splat + #flatten   191.960k i/100ms
         multi-splat   595.693k i/100ms
Calculating -------------------------------------
                  #+     11.380M (± 6.3%) i/s -     57.673M in   5.088056s
                 #+=      7.020M (± 7.5%) i/s -     35.321M in   5.060801s
             #concat      6.757M (± 4.3%) i/s -     33.951M in   5.033526s
                  #|      4.589M (± 7.1%) i/s -     23.145M in   5.069256s
      #<< + #flatten      1.995M (± 7.8%) i/s -     10.084M in   5.083410s
    splat + #flatten      1.987M (± 7.7%) i/s -      9.982M in   5.053069s
         multi-splat      6.018M (± 5.4%) i/s -     30.380M in   5.063797s

Comparison:
                  #+: 11379928.4 i/s
                 #+=:  7020046.0 i/s - 1.62x  slower
             #concat:  6757373.6 i/s - 1.68x  slower
         multi-splat:  6017673.7 i/s - 1.89x  slower
                  #|:  4588614.9 i/s - 2.48x  slower
      #<< + #flatten:  1995390.6 i/s - 5.70x  slower
    splat + #flatten:  1986837.6 i/s - 5.73x  slower

scripts/arrays/search

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

list = %w[one two three four five six seven eight nine ten]
pattern = /t/

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report("#grep") { list.grep pattern }
  benchmark.report("#select") { list.select { |value| value.match? pattern } }

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
               #grep   173.406k i/100ms
             #select   185.779k i/100ms
Calculating -------------------------------------
               #grep      1.771M (± 6.7%) i/s -      8.844M in   5.016015s
             #select      1.912M (± 5.5%) i/s -      9.661M in   5.067631s

Comparison:
             #select:  1912344.1 i/s
               #grep:  1770866.9 i/s - same-ish: difference falls within error

scripts/closures

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

Example = Class.new do
  def echo_implicit text
    yield
    text
  end

  def echo_implicit_guard text
    yield if block_given?
    text
  end

  def echo_explicit text, &block
    yield block
    text
  end

  def echo_explicit_guard text, &block
    yield block if block
    text
  end
end

block_example = Example.new
lambda_example = -> text { text }
proc_example = proc { |text| text }

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report "Block (implicit)" do
    block_example.echo_implicit("hi") { "test" }
  end

  benchmark.report "Block (implicit guard)" do
    block_example.echo_implicit_guard("hi") { "test" }
  end

  benchmark.report "Block (explicit)" do
    block_example.echo_explicit("hi") { "test" }
  end

  benchmark.report "Block (explicit guard)" do
    block_example.echo_explicit_guard("hi") { "test" }
  end

  benchmark.report "Lambda" do
    lambda_example.call "test"
  end

  benchmark.report "Proc" do
    proc_example.call "test"
  end

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
    Block (implicit)     3.719M i/100ms
Block (implicit guard)
                         3.997M i/100ms
    Block (explicit)   595.448k i/100ms
Block (explicit guard)
                       597.963k i/100ms
              Lambda     2.428M i/100ms
                Proc     2.563M i/100ms
Calculating -------------------------------------
    Block (implicit)     59.804M (± 1.0%) i/s -    301.203M in   5.037053s
Block (implicit guard)
                         59.083M (± 1.2%) i/s -    295.765M in   5.006599s
    Block (explicit)      6.377M (± 6.6%) i/s -     32.154M in   5.065513s
Block (explicit guard)
                          6.365M (± 5.7%) i/s -     32.290M in   5.090170s
              Lambda     34.880M (± 1.2%) i/s -    174.833M in   5.013192s
                Proc     35.295M (± 1.9%) i/s -    176.865M in   5.012838s

Comparison:
    Block (implicit): 59803662.6 i/s
Block (implicit guard): 59083369.8 i/s - same-ish: difference falls within error
                Proc: 35294734.0 i/s - 1.69x  slower
              Lambda: 34879827.8 i/s - 1.71x  slower
    Block (explicit):  6377470.8 i/s - 9.38x  slower
Block (explicit guard):  6365264.1 i/s - 9.40x  slower

scripts/constants/lookup

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

CONSTANTS = Hash.new

module Constants
  1_000.times { |index| CONSTANTS["EXAMPLE_#{index}"] = const_set "EXAMPLE_#{index}", index }
end

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report("#[]") { CONSTANTS["EXAMPLE_666"] }
  benchmark.report("Module.get (symbol)") { Constants.const_get :EXAMPLE_666 }
  benchmark.report("Module.get (string)") { Constants.const_get "EXAMPLE_666" }
  benchmark.report("Object.get") { Object.const_get "Constants::EXAMPLE_666" }

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
                 #[]     3.380M i/100ms
 Module.get (symbol)     3.491M i/100ms
 Module.get (string)     1.706M i/100ms
          Object.get     1.068M i/100ms
Calculating -------------------------------------
                 #[]     44.355M (± 0.2%) i/s -    223.075M in   5.029360s
 Module.get (symbol)     44.370M (± 0.1%) i/s -    223.435M in   5.035747s
 Module.get (string)     18.818M (± 0.5%) i/s -     95.510M in   5.075490s
          Object.get     11.076M (± 0.3%) i/s -     55.527M in   5.013445s

Comparison:
 Module.get (symbol): 44369850.1 i/s
                 #[]: 44354632.8 i/s - same-ish: difference falls within error
 Module.get (string): 18818346.2 i/s - 2.36x  slower
          Object.get: 11075724.0 i/s - 4.01x  slower

scripts/delegates

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

require "delegate"
require "forwardable"

module Echo
  def self.call(message) = message
end

class ForwardExample
  def initialize operation
    @operation = operation
  end

  def call(...) = operation.call(...)

  private

  attr_reader :operation
end

class DelegateExample
  extend Forwardable

  delegate %i[call] => :operation

  def initialize operation
    @operation = operation
  end

  private

  attr_reader :operation
end

class SimpleExample < SimpleDelegator
end

class ClassExample < DelegateClass Echo
end

message = "A test."
forward_example = ForwardExample.new Echo
deletate_example = DelegateExample.new Echo
simple_example = SimpleExample.new Echo
class_example = ClassExample.new Echo

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report("Forward") { forward_example.call message }
  benchmark.report("Delegate") { deletate_example.call message }
  benchmark.report("Simple Delegator") { simple_example.call message }
  benchmark.report("Delegate Class") { class_example.call message }

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
             Forward     1.132M i/100ms
            Delegate     1.017M i/100ms
    Simple Delegator   385.198k i/100ms
      Delegate Class   381.099k i/100ms
Calculating -------------------------------------
             Forward     13.380M (± 5.3%) i/s -     66.769M in   5.005398s
            Delegate     12.486M (± 3.0%) i/s -     63.053M in   5.054667s
    Simple Delegator      4.191M (± 7.0%) i/s -     21.186M in   5.077130s
      Delegate Class      4.174M (± 6.5%) i/s -     20.960M in   5.041277s

Comparison:
             Forward: 13380268.0 i/s
            Delegate: 12486453.4 i/s - same-ish: difference falls within error
    Simple Delegator:  4191347.1 i/s - 3.19x  slower
      Delegate Class:  4174068.4 i/s - 3.21x  slower

scripts/hashes/lookup

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

example = {a: 1, b: 2, c: 3}

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report("#[]") { example[:b] }
  benchmark.report("#fetch") { example.fetch :b }
  benchmark.report("#fetch (default)") { example.fetch :b, "default" }
  benchmark.report("#fetch (block)") { example.fetch(:b) { "default" } }
  benchmark.report("#dig") { example.dig :b }

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
                 #[]     4.035M i/100ms
              #fetch     3.642M i/100ms
    #fetch (default)     3.661M i/100ms
      #fetch (block)     3.639M i/100ms
                #dig     3.747M i/100ms
Calculating -------------------------------------
                 #[]     53.886M (± 0.1%) i/s -    270.328M in   5.016651s
              #fetch     45.478M (± 0.1%) i/s -    229.440M in   5.045124s
    #fetch (default)     45.677M (± 0.4%) i/s -    230.660M in   5.049861s
      #fetch (block)     44.675M (± 0.2%) i/s -    225.636M in   5.050641s
                #dig     50.390M (± 0.5%) i/s -    254.823M in   5.057135s

Comparison:
                 #[]: 53886256.9 i/s
                #dig: 50390168.1 i/s - 1.07x  slower
    #fetch (default): 45677292.4 i/s - 1.18x  slower
              #fetch: 45477689.6 i/s - 1.18x  slower
      #fetch (block): 44674922.3 i/s - 1.21x  slower

scripts/hashes/merge

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

extra = {b: 2}

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report("Splat") { {a: 1, **extra} }
  benchmark.report("Merge") { {a: 1}.merge extra }
  benchmark.report("Merge!") { {a: 1}.merge! extra }
  benchmark.report("Dup Merge!") { {a: 1}.dup.merge! extra }

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
               Splat   977.474k i/100ms
               Merge   651.230k i/100ms
              Merge!   979.604k i/100ms
          Dup Merge!   525.213k i/100ms
Calculating -------------------------------------
               Splat     10.458M (± 7.0%) i/s -     52.784M in   5.072085s
               Merge      6.637M (± 2.2%) i/s -     33.213M in   5.006663s
              Merge!     10.624M (± 6.7%) i/s -     53.878M in   5.093783s
          Dup Merge!      5.400M (± 7.0%) i/s -     27.311M in   5.081979s

Comparison:
              Merge!: 10624192.6 i/s
               Splat: 10457715.9 i/s - same-ish: difference falls within error
               Merge:  6636870.8 i/s - 1.60x  slower
          Dup Merge!:  5399502.4 i/s - 1.97x  slower

scripts/hashes/reduce

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

numbers = {
  one: 1,
  two: 2,
  three: 3,
  four: 4,
  five: 5,
  six: 6,
  seven: 7,
  eight: 8,
  nine: 9,
  ten: 10
}

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report "Reduce" do
    numbers.reduce({}) { |collection, (key, value)| collection.merge! value => key }
  end

  benchmark.report "With Object" do
    numbers.each.with_object({}) { |(key, value), collection| collection[value] = key }
  end

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
              Reduce    46.696k i/100ms
         With Object    86.950k i/100ms
Calculating -------------------------------------
              Reduce    469.369k (± 7.1%) i/s -      2.381M in   5.099251s
         With Object    893.855k (± 0.5%) i/s -      4.521M in   5.058420s

Comparison:
         With Object:   893855.0 i/s
              Reduce:   469368.6 i/s - 1.90x  slower

scripts/loops

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

collection = (1..1_000).to_a
sum = 0

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report "for" do
    for number in collection do
      sum += number
    end
  end

  benchmark.report "#each" do
    collection.each { |number| sum += number }
  end

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
                 for     6.369k i/100ms
               #each     6.583k i/100ms
Calculating -------------------------------------
                 for     63.745k (± 0.1%) i/s -    324.819k in   5.095574s
               #each     65.617k (± 0.5%) i/s -    329.150k in   5.016325s

Comparison:
               #each:    65617.4 i/s
                 for:    63745.4 i/s - 1.03x  slower

scripts/methods/define_method

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

require "forwardable"

Person = Class.new do
  def initialize first, last
    @first = first
    @last = last
  end

  def full_name
    "#{first} #{last}"
  end

  private

  attr_reader :first, :last
end

Example = Class.new Person do
  extend Forwardable

  define_method :unbound_full_name, Person.instance_method(:full_name)
  delegate %i[full_name] => :person

  def initialize first, last, person: Person.new(first, last)
    super first, last
    @person = person
  end

  def wrapped_full_name
    person.full_name
  end

  private

  attr_reader :first, :last, :person
end

example = Example.new "Jill", "Doe"

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report("Wrapped") { example.wrapped_full_name }
  benchmark.report("Defined") { example.unbound_full_name }
  benchmark.report("Delegated") { example.full_name }

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
             Wrapped     1.128M i/100ms
             Defined     1.153M i/100ms
           Delegated   626.507k i/100ms
Calculating -------------------------------------
             Wrapped     12.039M (± 4.8%) i/s -     60.917M in   5.072433s
             Defined     12.270M (± 3.9%) i/s -     62.244M in   5.080966s
           Delegated      6.912M (± 6.2%) i/s -     34.458M in   5.005586s

Comparison:
             Defined: 12269954.3 i/s
             Wrapped: 12039424.1 i/s - same-ish: difference falls within error
           Delegated:  6912331.3 i/s - 1.78x  slower

scripts/methods/method_proc

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

Example = Class.new do
  def initialize words
    @words = words
    @first_word = words.first
  end

  def direct_single
    say first_word
  end

  def direct_multiple
    words.each { |word| say word }
  end

  def proc_single
    method(:say).call first_word
  end

  def proc_multiple
    words.each { |word| method(:say).call word }
  end

  def method_to_proc_single
    first_word.then(&method(:say))
  end

  def method_to_proc_multiple
    words.each(&method(:say))
  end

  private

  attr_reader :words, :first_word

  def say phrase
    "You said: #{phrase}."
  end
end

example = Example.new %w[one two three]

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report("Direct (s)") { example.direct_single }
  benchmark.report("Direct (m)") { example.direct_multiple }
  benchmark.report("Proc (s)") { example.proc_single }
  benchmark.report("Proc (m)") { example.proc_multiple }
  benchmark.report("Method To Proc (s)") { example.method_to_proc_single }
  benchmark.report("Method To Proc (m)") { example.method_to_proc_multiple }

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
          Direct (s)     1.154M i/100ms
          Direct (m)   391.284k i/100ms
            Proc (s)   547.383k i/100ms
            Proc (m)   167.519k i/100ms
  Method To Proc (s)   260.729k i/100ms
  Method To Proc (m)   176.733k i/100ms
Calculating -------------------------------------
          Direct (s)     12.832M (± 4.1%) i/s -     64.611M in   5.043941s
          Direct (m)      4.159M (± 4.2%) i/s -     21.129M in   5.088977s
            Proc (s)      6.454M (± 6.0%) i/s -     32.296M in   5.023367s
            Proc (m)      1.830M (± 3.9%) i/s -      9.214M in   5.043206s
  Method To Proc (s)      2.874M (± 3.4%) i/s -     14.601M in   5.086253s
  Method To Proc (m)      1.962M (± 3.3%) i/s -      9.897M in   5.048962s

Comparison:
          Direct (s): 12831929.9 i/s
            Proc (s):  6453824.9 i/s - 1.99x  slower
          Direct (m):  4158707.8 i/s - 3.09x  slower
  Method To Proc (s):  2874279.1 i/s - 4.46x  slower
  Method To Proc (m):  1962181.8 i/s - 6.54x  slower
            Proc (m):  1829728.8 i/s - 7.01x  slower

scripts/methods/send

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

module Static
  def self.call = rand > 0.5 ? one : two

  def self.one = 1

  def self.two = 2
end

module Dynamic
  def self.with_strings = public_send rand > 0.5 ? "one" : "two"

  def self.with_symbols = public_send rand > 0.5 ? :one : :two

  def self.one = 1

  def self.two = 2
end

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2
  max = 1_000_000

  benchmark.report("Static") { max.times { Static.call } }
  benchmark.report("Dynamic (strings)") { max.times { Dynamic.with_strings } }
  benchmark.report("Dynamic (symbols)") { max.times { Dynamic.with_symbols } }

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
              Static     2.000 i/100ms
   Dynamic (strings)     1.000 i/100ms
   Dynamic (symbols)     1.000 i/100ms
Calculating -------------------------------------
              Static     27.041 (± 0.0%) i/s -    136.000 in   5.029371s
   Dynamic (strings)     11.600 (± 0.0%) i/s -     59.000 in   5.086066s
   Dynamic (symbols)     15.589 (± 0.0%) i/s -     78.000 in   5.003651s

Comparison:
              Static:       27.0 i/s
   Dynamic (symbols):       15.6 i/s - 1.73x  slower
   Dynamic (strings):       11.6 i/s - 2.33x  slower

scripts/numerics

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "bigdecimal"
  gem "benchmark-ips"
end

require "bigdecimal"

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report("Integer") { 1 + 0 }
  benchmark.report("Float") { 0.0001 + 0 }
  benchmark.report("Rational") { (1 / 1000r) + 0 }
  benchmark.report("BigDecimal") { BigDecimal("0.0001") + 0 }

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
             Integer     4.945M i/100ms
               Float     3.898M i/100ms
            Rational   957.175k i/100ms
          BigDecimal   226.746k i/100ms
Calculating -------------------------------------
             Integer     75.084M (± 4.5%) i/s -    375.824M in   5.021257s
               Float     60.640M (± 0.6%) i/s -    304.039M in   5.013994s
            Rational     10.254M (± 0.6%) i/s -     51.687M in   5.040700s
          BigDecimal      2.332M (± 4.5%) i/s -     11.791M in   5.066520s

Comparison:
             Integer: 75083686.6 i/s
               Float: 60640359.1 i/s - 1.24x  slower
            Rational: 10254439.4 i/s - 7.32x  slower
          BigDecimal:  2332151.1 i/s - 32.20x  slower

scripts/refinements/import

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

module Import
  def dud = true
end

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report "With" do
    Module.new { refine(String) { import_methods Import } }
  end

  benchmark.report "Without" do
    Module.new { def dud = true }
  end

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
                With     1.438k i/100ms
             Without   365.822k i/100ms
Calculating -------------------------------------
                With     21.970k (±190.8%) i/s -     43.140k in   5.067653s
             Without      3.636M (± 7.1%) i/s -     18.291M in   5.054830s

Comparison:
             Without:  3635623.1 i/s
                With:    21969.9 i/s - 165.48x  slower

scripts/refinements/initialize

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

module Refines
  refine String do
    def dud = true
  end
end

class With
  using Refines

  def initialize value = "demo"
    @value = value
  end
end

class Without
  def initialize value = "demo"
    @value = value
  end
end

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report("With") { With.new }
  benchmark.report("Without") { Without.new }

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
                With     1.118M i/100ms
             Without     1.067M i/100ms
Calculating -------------------------------------
                With     12.286M (± 5.5%) i/s -     61.467M in   5.019067s
             Without     11.971M (± 5.7%) i/s -     59.775M in   5.010076s

Comparison:
                With: 12285927.1 i/s
             Without: 11970917.9 i/s - same-ish: difference falls within error

scripts/refinements/message

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

module Refines
  refine String do
    def dud = true
  end
end

module With
  using Refines

  def self.call(value) = value.dud
end

module Without
  def self.call(value) = value
end

value = "demo"

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report("With") { With.call value }
  benchmark.report("Without") { Without.call value }

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
                With     2.681M i/100ms
             Without     3.796M i/100ms
Calculating -------------------------------------
                With     37.893M (± 1.2%) i/s -    190.353M in   5.024141s
             Without     59.787M (± 4.2%) i/s -    299.874M in   5.024598s

Comparison:
             Without: 59786895.6 i/s
                With: 37893449.5 i/s - 1.58x  slower

scripts/refinements/refine

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report "With" do
    Module.new do
      refine String do
        def dud = true
      end
    end
  end

  benchmark.report "Without" do
    Module.new do
      def dud = true
    end
  end

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
                With     1.497k i/100ms
             Without   337.437k i/100ms
Calculating -------------------------------------
                With     21.225k (±192.4%) i/s -     35.928k in   5.093624s
             Without      3.284M (± 8.5%) i/s -     16.534M in   5.067713s

Comparison:
             Without:  3284418.0 i/s
                With:    21224.8 i/s - 154.74x  slower

scripts/strings/concatenation

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

one = "One"
two = "Two"
three = "Three"
four = "Four"
five = "Five"
six = "Six"
seven = "Seven"
eight = "Eight"
nine = "Nine"
ten = "Ten"

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report "Implicit (<)" do
    "One" "Two"
  end

  benchmark.report "Implicit (>)" do
    "One" "Two" "Three" "Four" "Five" "Six" "Seven" "Eight" "Nine" "Ten"
  end

  benchmark.report "Interpolation (<)" do
    "#{one} #{two}"
  end

  benchmark.report "Interpolation (>)" do
    "#{one} #{two} #{three} #{four} #{five} #{six} #{seven} #{eight} #{nine} #{ten}"
  end

  benchmark.report "#+ (<)" do
    one + " " + two
  end

  benchmark.report "#+ (>)" do
    one + " " + two + " " + three + " " + four + " " + five + " " + six + " " + seven + " " +
    eight + " " + nine + " " + ten
  end

  # WARNING: Mutation.
  benchmark.report "#concat (<)" do
    one.dup.concat two
  end

  # WARNING: Mutation.
  benchmark.report "#concat (>)" do
    one.dup.concat two, three, four, five, six, seven, eight, nine, ten
  end

  # WARNING: Mutation.
  benchmark.report "#<< (<)" do
    one.dup << two
  end

  # WARNING: Mutation.
  benchmark.report "#<< (>)" do
    one.dup << two << three << four << five << six << seven << eight << nine << ten
  end

  benchmark.report "Array#join (<)" do
    [one, two].join " "
  end

  benchmark.report "Array#join (>)" do
    [one, two, three, four, five, six, seven, eight, nine, ten].join " "
  end

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
        Implicit (<)     4.372M i/100ms
        Implicit (>)     4.448M i/100ms
   Interpolation (<)     1.208M i/100ms
   Interpolation (>)   367.925k i/100ms
              #+ (<)   823.216k i/100ms
              #+ (>)    80.833k i/100ms
         #concat (<)     1.369M i/100ms
         #concat (>)   291.617k i/100ms
             #<< (<)     1.479M i/100ms
             #<< (>)   459.494k i/100ms
      Array#join (<)   722.879k i/100ms
      Array#join (>)   307.222k i/100ms
Calculating -------------------------------------
        Implicit (<)     75.685M (± 1.7%) i/s -    380.338M in   5.026812s
        Implicit (>)     75.105M (± 1.8%) i/s -    378.120M in   5.036297s
   Interpolation (<)     12.722M (± 5.7%) i/s -     64.005M in   5.047960s
   Interpolation (>)      3.868M (± 5.9%) i/s -     19.500M in   5.057509s
              #+ (<)      8.701M (± 7.1%) i/s -     43.630M in   5.038462s
              #+ (>)    837.724k (± 7.7%) i/s -      4.203M in   5.048670s
         #concat (<)     14.496M (± 8.5%) i/s -     72.583M in   5.045357s
         #concat (>)      2.959M (± 8.8%) i/s -     14.872M in   5.063294s
             #<< (<)     15.944M (± 8.0%) i/s -     79.888M in   5.043131s
             #<< (>)      4.739M (± 9.0%) i/s -     23.894M in   5.082877s
      Array#join (<)      7.392M (± 7.9%) i/s -     36.867M in   5.018258s
      Array#join (>)      3.275M (± 7.8%) i/s -     16.283M in   5.000238s

Comparison:
        Implicit (<): 75684765.3 i/s
        Implicit (>): 75104861.7 i/s - same-ish: difference falls within error
             #<< (<): 15944068.0 i/s - 4.75x  slower
         #concat (<): 14496048.1 i/s - 5.22x  slower
   Interpolation (<): 12722385.9 i/s - 5.95x  slower
              #+ (<):  8700892.4 i/s - 8.70x  slower
      Array#join (<):  7392065.5 i/s - 10.24x  slower
             #<< (>):  4738942.8 i/s - 15.97x  slower
   Interpolation (>):  3867990.4 i/s - 19.57x  slower
      Array#join (>):  3274997.2 i/s - 23.11x  slower
         #concat (>):  2959275.8 i/s - 25.58x  slower
              #+ (>):   837724.4 i/s - 90.35x  slower

scripts/strings/matching

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

require "securerandom"

word = SecureRandom.alphanumeric 100
string_matcher = "a"
regex_matcher = /\Aa/

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report("#match?") { word.match? regex_matcher }
  benchmark.report("#=~") { word =~ regex_matcher }
  benchmark.report("#start_with? (String)") { word.start_with? string_matcher }
  benchmark.report("#start_with? (Regex)") { word.start_with? regex_matcher }
  benchmark.report("#end_with?") { word.end_with? string_matcher }

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
             #match?     2.422M i/100ms
                 #=~   742.580k i/100ms
#start_with? (String)
                         2.965M i/100ms
#start_with? (Regex)   679.369k i/100ms
          #end_with?     3.069M i/100ms
Calculating -------------------------------------
             #match?     28.797M (± 5.0%) i/s -    145.346M in   5.060238s
                 #=~      7.620M (± 9.1%) i/s -     37.872M in   5.008839s
#start_with? (String)
                         34.312M (± 5.0%) i/s -    171.982M in   5.024675s
#start_with? (Regex)      7.464M (± 7.4%) i/s -     37.365M in   5.034025s
          #end_with?     37.141M (± 0.5%) i/s -    187.219M in   5.040811s

Comparison:
          #end_with?: 37141486.2 i/s
#start_with? (String): 34312493.3 i/s - 1.08x  slower
             #match?: 28797216.7 i/s - 1.29x  slower
                 #=~:  7620209.0 i/s - 4.87x  slower
#start_with? (Regex):  7463669.2 i/s - 4.98x  slower

scripts/strings/split

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

require "securerandom"

words = Array.new(100_000) { SecureRandom.alphanumeric 10 }
delimiter = " "
text = words.join delimiter
pattern = /\Aa/

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report "Without Block" do
    text.split(delimiter).grep(pattern)
  end

  benchmark.report "With Block" do
    selections = []
    text.split(delimiter) { |word| selections << word if word.match? pattern }
  end

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
       Without Block    13.000 i/100ms
          With Block    13.000 i/100ms
Calculating -------------------------------------
       Without Block    136.799 (± 2.2%) i/s -    689.000 in   5.039420s
          With Block    134.538 (± 2.2%) i/s -    676.000 in   5.026808s

Comparison:
       Without Block:      136.8 i/s
          With Block:      134.5 i/s - same-ish: difference falls within error

scripts/strings/substrings

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

example = "example"

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report("#sub (string)") { example.sub "x", "b" }
  benchmark.report("#sub (regex)") { example.sub(/x/, "b") }
  benchmark.report("#gsub (string)") { example.gsub "x", "b" }
  benchmark.report("#gsub (regex)") { example.gsub(/x/, "b") }
  benchmark.report("#tr") { example.tr "x", "b" }

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
       #sub (string)   484.505k i/100ms
        #sub (regex)   344.692k i/100ms
      #gsub (string)   414.555k i/100ms
       #gsub (regex)   170.417k i/100ms
                 #tr     1.007M i/100ms
Calculating -------------------------------------
       #sub (string)      5.449M (± 3.0%) i/s -     27.617M in   5.073218s
        #sub (regex)      4.021M (± 6.1%) i/s -     20.337M in   5.074883s
      #gsub (string)      4.463M (± 3.4%) i/s -     22.386M in   5.021307s
       #gsub (regex)      1.710M (± 8.6%) i/s -      8.521M in   5.018878s
                 #tr     10.693M (± 2.8%) i/s -     54.352M in   5.087263s

Comparison:
                 #tr: 10692642.6 i/s
       #sub (string):  5448640.0 i/s - 1.96x  slower
      #gsub (string):  4463049.2 i/s - 2.40x  slower
        #sub (regex):  4020798.1 i/s - 2.66x  slower
       #gsub (regex):  1710379.3 i/s - 6.25x  slower

scripts/thens

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report "standard" do
    one, two = "one two".split
    "#{one} + #{two} = #{one + two}"
  end

  benchmark.report "then" do
    "one two".split.then { |one, two| "#{one} + #{two} = #{one + two}" }
  end

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
            standard   361.533k i/100ms
                then   340.568k i/100ms
Calculating -------------------------------------
            standard      3.566M (± 7.3%) i/s -     18.077M in   5.096489s
                then      3.366M (± 7.6%) i/s -     17.028M in   5.088830s

Comparison:
            standard:  3566064.8 i/s
                then:  3365922.3 i/s - same-ish: difference falls within error

scripts/values/inheritance

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

PlotStruct = Struct.new :x, :y

class PlotSubclass < Struct.new :x, :y
end

struct = -> { PlotStruct[x: 1, y: 2] }
subclass = -> { PlotSubclass[x: 1, y: 2] }

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report("Struct") { struct.call }
  benchmark.report("Subclass") { subclass.call }

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
              Struct   446.439k i/100ms
            Subclass   438.472k i/100ms
Calculating -------------------------------------
              Struct      4.925M (± 7.6%) i/s -     24.554M in   5.013174s
            Subclass      4.736M (± 7.8%) i/s -     23.677M in   5.027862s

Comparison:
              Struct:  4924540.0 i/s
            Subclass:  4736109.1 i/s - same-ish: difference falls within error

scripts/values/initialization

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
  gem "dry-struct"
end

Warning[:performance] = false

require "ostruct"

DataDefault = Data.define :a, :b, :c, :d, :e

DataCustom = Data.define :a, :b, :c, :d, :e do
  def initialize a: 1, b: 2, c: 3, d: 4, e: 5
    super
  end
end

StructDefault = Struct.new :a, :b, :c, :d, :e

StructCustom = Struct.new :a, :b, :c, :d, :e do
  def initialize a: 1, b: 2, c: 3, d: 4, e: 5
    super
  end
end

module Types
  include Dry.Types
end

DryExample = Class.new Dry::Struct do
  attribute :a, Types::Strict::Integer
  attribute :b, Types::Strict::Integer
  attribute :c, Types::Strict::Integer
  attribute :d, Types::Strict::Integer
  attribute :e, Types::Strict::Integer
end

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report("Data (positional)") { DataDefault[1, 2, 3, 4, 5] }
  benchmark.report("Data (keyword)") { DataDefault[a: 1, b: 2, c: 3, d: 4, e: 5] }
  benchmark.report("Data (custom)") { DataCustom.new }
  benchmark.report("Struct (positional)") { StructDefault[1, 2, 3, 4, 5] }
  benchmark.report("Struct (keyword)") { StructDefault[a: 1, b: 2, c: 3, d: 4, e: 5] }
  benchmark.report("Struct (custom)") { StructCustom.new }
  benchmark.report("OpenStruct") { OpenStruct.new a: 1, b: 2, c: 3, d: 4, e: 5 }
  benchmark.report("Dry Struct") { DryExample[a: 1, b: 2, c: 3, d: 4, e: 5] }

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
   Data (positional)   304.848k i/100ms
      Data (keyword)   317.019k i/100ms
       Data (custom)   224.669k i/100ms
 Struct (positional)   722.167k i/100ms
    Struct (keyword)   302.062k i/100ms
     Struct (custom)   292.850k i/100ms
          OpenStruct   743.000 i/100ms
          Dry Struct   111.304k i/100ms
Calculating -------------------------------------
   Data (positional)      3.120M (± 9.7%) i/s -     15.547M in   5.025792s
      Data (keyword)      3.191M (± 9.7%) i/s -     15.851M in   5.008582s
       Data (custom)      2.226M (± 3.6%) i/s -     11.233M in   5.054091s
 Struct (positional)      6.869M (±10.4%) i/s -     34.664M in   5.098769s
    Struct (keyword)      3.059M (± 9.6%) i/s -     15.405M in   5.077727s
     Struct (custom)      2.945M (±11.4%) i/s -     14.642M in   5.031281s
          OpenStruct      1.856k (±24.0%) i/s -      9.659k in   5.485483s
          Dry Struct      1.123M (±11.8%) i/s -      5.565M in   5.029750s

Comparison:
 Struct (positional):  6868838.9 i/s
      Data (keyword):  3191343.7 i/s - 2.15x  slower
   Data (positional):  3120155.8 i/s - 2.20x  slower
    Struct (keyword):  3059412.6 i/s - 2.25x  slower
     Struct (custom):  2945238.4 i/s - 2.33x  slower
       Data (custom):  2225751.7 i/s - 3.09x  slower
          Dry Struct:  1123063.3 i/s - 6.12x  slower
          OpenStruct:     1855.9 i/s - 3701.01x  slower

ℹ️ `Data` is fastest when members are small (like three or less) but performance degrades when more members are added (like five or more). This is because `Data` always initializes with a `Hash` which is not the case with a `Struct`. Additionally, passing keyword arguments to/from Ruby to Ruby is optimized while to/from Ruby/C is not.

scripts/values/reading

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
  gem "dry-struct"
end

require "ostruct"

DataExample = Data.define :to, :from
StructExample = Struct.new :to, :from

module Types
  include Dry.Types
end

DryExample = Class.new Dry::Struct do
  attribute :to, Types::Strict::String
  attribute :from, Types::Strict::String
end

data = DataExample[to: "Rick", from: "Morty"]
struct = StructExample[to: "Rick", from: "Morty"]
open_struct = OpenStruct.new to: "Rick", from: "Morty"
dry_struct = DryExample[to: "Rick", from: "Morty"]

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report("Data") { data.to }
  benchmark.report("Struct") { struct.to }
  benchmark.report("OpenStruct") { open_struct.to }
  benchmark.report("Dry Struct") { dry_struct.to }

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
                Data     4.313M i/100ms
              Struct     3.960M i/100ms
          OpenStruct     3.419M i/100ms
          Dry Struct     3.646M i/100ms
Calculating -------------------------------------
                Data     72.832M (± 0.1%) i/s -    366.637M in   5.034027s
              Struct     68.730M (± 0.4%) i/s -    344.554M in   5.013242s
          OpenStruct     49.555M (± 0.1%) i/s -    249.580M in   5.036400s
          Dry Struct     49.635M (± 0.1%) i/s -    251.578M in   5.068604s

Comparison:
                Data: 72831756.3 i/s
              Struct: 68729971.7 i/s - 1.06x  slower
          Dry Struct: 49634683.0 i/s - 1.47x  slower
          OpenStruct: 49555343.3 i/s - 1.47x  slower

scripts/values/writing

Source

#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "benchmark-ips"
end

require "ostruct"

DataExample = Data.define :to, :from
StructExample = Struct.new :to, :from

data = DataExample[to: "Rick", from: "Morty"]
struct = StructExample[to: "Rick", from: "Morty"]
open_struct = OpenStruct.new to: "Rick", from: "Morty"

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report("Data") { data.with from: "Summer" }
  benchmark.report("Struct") { struct.from = "Summer" }
  benchmark.report("OpenStruct") { open_struct.from = "Summer" }

  benchmark.compare!
end

Benchmark

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23.4.0]
Warming up --------------------------------------
                Data   265.287k i/100ms
              Struct     4.274M i/100ms
          OpenStruct     2.870M i/100ms
Calculating -------------------------------------
                Data      2.721M (± 8.0%) i/s -     13.530M in   5.002940s
              Struct     55.482M (± 1.5%) i/s -    277.793M in   5.008024s
          OpenStruct     34.810M (± 0.2%) i/s -    175.090M in   5.029864s

Comparison:
              Struct: 55482353.9 i/s
          OpenStruct: 34810159.3 i/s - 1.59x  slower
                Data:  2721426.3 i/s - 20.39x  slower

Development

To contribute, run:

git clone https://github.com/bkuhlmann/benchmarks.git
cd benchmarks
bin/setup

To render documentation for all benchmark scripts, run:

bin/render

This is the same script used to update the documentation within this README.

Tests

To test, run:

bin/rake

Credits