An open API service indexing awesome lists of open source software.

https://github.com/bkuhlmann/benchmarks

A collection of micro benchmarks.
https://github.com/bkuhlmann/benchmarks

benchmark micro speed

Last synced: 6 months ago
JSON representation

A collection of micro benchmarks.

Awesome Lists containing this project

README

          

:toc: macro
:toclevels: 5
:figure-caption!:

= Benchmarks

Benchmarks is a collection of Ruby micro benchmarks which can be cloned and run locally or used as
an information point of reference. The various statistics on Ruby performance captured within this
project may or may not surprise you.

toc::[]

== Features

* Uses link:https://github.com/evanphx/benchmark-ips[Benchmark IPS] to calculate CPU/speed results.
* Each script is independently executable.

== Requirements

. link:https://www.ruby-lang.org[Ruby]

== Setup

To install, run:

[source,bash]
----
git clone https://github.com/bkuhlmann/benchmarks.git
cd benchmarks
git checkout 5.2.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/arrays/concatenation

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
#+ 2.130M i/100ms
#+= 927.861k i/100ms
#concat 691.057k i/100ms
#| 483.602k i/100ms
#<< + #flatten 203.055k i/100ms
splat + #flatten 212.664k i/100ms
multi-splat 903.880k i/100ms
Calculating -------------------------------------
#+ 23.159M (± 0.7%) i/s (43.18 ns/i) - 117.130M in 5.057953s
#+= 9.970M (± 0.8%) i/s (100.30 ns/i) - 50.104M in 5.025637s
#concat 7.333M (± 1.6%) i/s (136.37 ns/i) - 37.317M in 5.090383s
#| 4.824M (± 1.8%) i/s (207.30 ns/i) - 24.180M in 5.014471s
#<< + #flatten 2.101M (± 1.1%) i/s (475.99 ns/i) - 10.559M in 5.026496s
splat + #flatten 2.156M (± 1.2%) i/s (463.80 ns/i) - 10.846M in 5.031006s
multi-splat 9.786M (± 2.0%) i/s (102.19 ns/i) - 49.713M in 5.082000s

Comparison:
#+: 23158921.5 i/s
#+=: 9970346.4 i/s - 2.32x slower
multi-splat: 9786149.7 i/s - 2.37x slower
#concat: 7332786.6 i/s - 3.16x slower
#|: 4823810.9 i/s - 4.80x slower
splat + #flatten: 2156124.7 i/s - 10.74x slower
#<< + #flatten: 2100889.3 i/s - 11.02x slower
....

=== scripts/arrays/search

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
#grep 198.448k i/100ms
#select 237.432k i/100ms
Calculating -------------------------------------
#grep 1.997M (± 1.3%) i/s (500.69 ns/i) - 10.121M in 5.068227s
#select 2.468M (± 0.9%) i/s (405.26 ns/i) - 12.346M in 5.003928s

Comparison:
#select: 2467557.1 i/s
#grep: 1997260.3 i/s - 1.24x slower
....

=== scripts/bindings

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

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

gem "benchmark-ips"
end

module Test
def self.with_binding(end:) = binding.local_variable_get(:end)

def self.with_pinning(end:) = {end:}[:end]
end

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

benchmark.report("Binding") { Test.with_binding end: 1 }
benchmark.report("Pinning") { Test.with_pinning end: 1 }

benchmark.compare!
end
----
====

*Benchmark*

....
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
Binding 685.023k i/100ms
Pinning 1.868M i/100ms
Calculating -------------------------------------
Binding 7.416M (± 2.1%) i/s (134.85 ns/i) - 37.676M in 5.082785s
Pinning 20.890M (± 2.7%) i/s (47.87 ns/i) - 104.605M in 5.011139s

Comparison:
Pinning: 20890279.6 i/s
Binding: 7415714.9 i/s - 2.82x slower
....

=== scripts/constants/lookup

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
#[] 2.897M i/100ms
Module.get (symbol) 3.406M i/100ms
Module.get (string) 1.669M i/100ms
Object.get 1.011M i/100ms
Calculating -------------------------------------
#[] 33.548M (± 0.9%) i/s (29.81 ns/i) - 168.050M in 5.009641s
Module.get (symbol) 42.820M (± 0.1%) i/s (23.35 ns/i) - 214.596M in 5.011591s
Module.get (string) 18.319M (± 0.3%) i/s (54.59 ns/i) - 91.822M in 5.012443s
Object.get 11.053M (± 0.2%) i/s (90.48 ns/i) - 55.582M in 5.028808s

Comparison:
Module.get (symbol): 42820077.0 i/s
#[]: 33548240.7 i/s - 1.28x slower
Module.get (string): 18319033.6 i/s - 2.34x slower
Object.get: 11052680.4 i/s - 3.87x slower
....

=== scripts/delegates

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
Forward 2.172M i/100ms
Delegate 2.005M i/100ms
Simple Delegator 494.121k i/100ms
Delegate Class 486.884k i/100ms
Calculating -------------------------------------
Forward 26.423M (± 0.2%) i/s (37.85 ns/i) - 132.481M in 5.013922s
Delegate 23.553M (± 0.5%) i/s (42.46 ns/i) - 118.295M in 5.022589s
Simple Delegator 5.399M (± 0.9%) i/s (185.23 ns/i) - 27.177M in 5.034498s
Delegate Class 5.404M (± 0.6%) i/s (185.04 ns/i) - 27.266M in 5.045399s

Comparison:
Forward: 26422769.4 i/s
Delegate: 23553279.8 i/s - 1.12x slower
Delegate Class: 5404232.5 i/s - 4.89x slower
Simple Delegator: 5398560.6 i/s - 4.89x slower
....

=== scripts/functions/blocks

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
Block (implicit) 4.215M i/100ms
Block (implicit guard)
3.820M i/100ms
Block (explicit) 787.233k i/100ms
Block (explicit guard)
783.153k i/100ms
Lambda 2.806M i/100ms
Proc 2.983M i/100ms
Calculating -------------------------------------
Block (implicit) 59.814M (± 0.2%) i/s (16.72 ns/i) - 299.269M in 5.003373s
Block (implicit guard)
56.952M (± 0.4%) i/s (17.56 ns/i) - 286.516M in 5.030941s
Block (explicit) 8.892M (± 1.0%) i/s (112.46 ns/i) - 44.872M in 5.046771s
Block (explicit guard)
8.804M (± 1.0%) i/s (113.59 ns/i) - 44.640M in 5.070946s
Lambda 34.991M (± 0.9%) i/s (28.58 ns/i) - 176.772M in 5.052370s
Proc 35.366M (± 0.9%) i/s (28.28 ns/i) - 178.993M in 5.061491s

Comparison:
Block (implicit): 59813544.8 i/s
Block (implicit guard): 56951704.4 i/s - 1.05x slower
Proc: 35366437.5 i/s - 1.69x slower
Lambda: 34990583.2 i/s - 1.71x slower
Block (explicit): 8892108.2 i/s - 6.73x slower
Block (explicit guard): 8803962.5 i/s - 6.79x slower
....

=== scripts/functions/static

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

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

gem "benchmark-ips"
end

class_example = Class.new do
def self.call(first, second) = first + second
end

module_example = Module.new do
module_function

def call(first, second) = first + second
end

function_example = -> first, second { first + second }

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

benchmark.report("Class") { class_example.call 10, 20 }
benchmark.report("Module") { module_example.call 10, 20 }
benchmark.report("Function") { function_example.call 10, 20 }

benchmark.compare!
end
----
====

*Benchmark*

....
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
Class 4.586M i/100ms
Module 4.072M i/100ms
Function 2.714M i/100ms
Calculating -------------------------------------
Class 62.525M (± 0.4%) i/s (15.99 ns/i) - 316.450M in 5.061239s
Module 62.654M (± 0.2%) i/s (15.96 ns/i) - 313.576M in 5.004938s
Function 31.392M (± 2.1%) i/s (31.86 ns/i) - 157.390M in 5.015900s

Comparison:
Module: 62653616.0 i/s
Class: 62525065.5 i/s - same-ish: difference falls within error
Function: 31391523.8 i/s - 2.00x slower
....

=== scripts/hashes/lookup

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
#[] 4.108M i/100ms
#fetch 3.613M i/100ms
#fetch (default) 3.746M i/100ms
#fetch (block) 3.709M i/100ms
#dig 4.047M i/100ms
Calculating -------------------------------------
#[] 51.006M (± 0.3%) i/s (19.61 ns/i) - 258.811M in 5.074111s
#fetch 44.278M (± 0.6%) i/s (22.58 ns/i) - 224.011M in 5.059357s
#fetch (default) 44.317M (± 0.3%) i/s (22.56 ns/i) - 224.757M in 5.071557s
#fetch (block) 43.807M (± 0.4%) i/s (22.83 ns/i) - 222.555M in 5.080432s
#dig 48.337M (± 0.1%) i/s (20.69 ns/i) - 242.803M in 5.023111s

Comparison:
#[]: 51006430.3 i/s
#dig: 48337145.4 i/s - 1.06x slower
#fetch (default): 44317430.9 i/s - 1.15x slower
#fetch: 44278312.1 i/s - 1.15x slower
#fetch (block): 43807023.1 i/s - 1.16x slower
....

=== scripts/hashes/merge

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
Splat 1.220M i/100ms
Merge 972.824k i/100ms
Merge! 1.398M i/100ms
Dup Merge! 728.688k i/100ms
Calculating -------------------------------------
Splat 14.035M (± 1.5%) i/s (71.25 ns/i) - 70.757M in 5.042521s
Merge 10.595M (± 1.4%) i/s (94.38 ns/i) - 53.505M in 5.050960s
Merge! 14.980M (± 1.2%) i/s (66.76 ns/i) - 75.471M in 5.038965s
Dup Merge! 7.787M (± 1.1%) i/s (128.41 ns/i) - 39.349M in 5.053630s

Comparison:
Merge!: 14979505.8 i/s
Splat: 14035287.2 i/s - 1.07x slower
Merge: 10595050.3 i/s - 1.41x slower
Dup Merge!: 7787275.3 i/s - 1.92x slower
....

=== scripts/hashes/reduce

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
Reduce 64.415k i/100ms
With Object 100.991k i/100ms
Calculating -------------------------------------
Reduce 656.529k (± 1.0%) i/s (1.52 μs/i) - 3.285M in 5.004376s
With Object 1.032M (± 0.8%) i/s (969.23 ns/i) - 5.252M in 5.090269s

Comparison:
With Object: 1031743.5 i/s
Reduce: 656529.5 i/s - 1.57x slower
....

=== scripts/loops

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
for 19.313k i/100ms
#each 21.611k i/100ms
Calculating -------------------------------------
for 193.383k (± 0.1%) i/s (5.17 μs/i) - 984.963k in 5.093344s
#each 215.690k (± 0.1%) i/s (4.64 μs/i) - 1.081M in 5.009754s

Comparison:
#each: 215689.6 i/s
for: 193382.6 i/s - 1.12x slower
....

=== scripts/methods/define_method

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
Wrapped 1.582M i/100ms
Defined 1.639M i/100ms
Delegated 1.150M i/100ms
Calculating -------------------------------------
Wrapped 17.189M (± 1.0%) i/s (58.18 ns/i) - 87.037M in 5.064110s
Defined 17.717M (± 1.0%) i/s (56.44 ns/i) - 90.126M in 5.087374s
Delegated 12.469M (± 0.5%) i/s (80.20 ns/i) - 63.277M in 5.074680s

Comparison:
Defined: 17717413.2 i/s
Wrapped: 17188761.0 i/s - 1.03x slower
Delegated: 12469384.0 i/s - 1.42x slower
....

=== scripts/methods/method_proc

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
Direct (s) 1.686M i/100ms
Direct (m) 581.545k i/100ms
Proc (s) 771.008k i/100ms
Proc (m) 277.976k i/100ms
Method To Proc (s) 351.620k i/100ms
Method To Proc (m) 222.732k i/100ms
Calculating -------------------------------------
Direct (s) 18.494M (± 1.0%) i/s (54.07 ns/i) - 92.704M in 5.013031s
Direct (m) 6.220M (± 1.4%) i/s (160.76 ns/i) - 31.403M in 5.049559s
Proc (s) 8.887M (± 0.9%) i/s (112.53 ns/i) - 44.718M in 5.032556s
Proc (m) 2.913M (± 1.0%) i/s (343.32 ns/i) - 14.733M in 5.058585s
Method To Proc (s) 3.771M (± 1.5%) i/s (265.18 ns/i) - 18.987M in 5.036310s
Method To Proc (m) 2.298M (± 0.9%) i/s (435.17 ns/i) - 11.582M in 5.040560s

Comparison:
Direct (s): 18494466.4 i/s
Proc (s): 8886651.1 i/s - 2.08x slower
Direct (m): 6220285.8 i/s - 2.97x slower
Method To Proc (s): 3770971.6 i/s - 4.90x slower
Proc (m): 2912720.9 i/s - 6.35x slower
Method To Proc (m): 2297978.5 i/s - 8.05x slower
....

=== scripts/methods/scoped_send

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

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

gem "benchmark-ips"
end

module Example
module_function

def add(first = 1, second = 2) = first + second
end

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

benchmark.report("Public") { Example.public_send :add }
benchmark.report("Private") { Example.__send__ :add }

benchmark.compare!
end
----
====

*Benchmark*

....
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
Public 2.011M i/100ms
Private 4.220M i/100ms
Calculating -------------------------------------
Public 23.979M (± 1.1%) i/s (41.70 ns/i) - 120.645M in 5.031991s
Private 61.137M (± 0.4%) i/s (16.36 ns/i) - 308.063M in 5.038936s

Comparison:
Private: 61137450.6 i/s
Public: 23978548.9 i/s - 2.55x slower
....

=== scripts/methods/send

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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

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

benchmark.compare!
end
----
====

*Benchmark*

....
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
Static 2.126M i/100ms
Dynamic (strings) 973.273k i/100ms
Dynamic (symbols) 1.256M i/100ms
Calculating -------------------------------------
Static 26.137M (± 5.6%) i/s (38.26 ns/i) - 131.819M in 5.058540s
Dynamic (strings) 11.631M (± 0.9%) i/s (85.98 ns/i) - 58.396M in 5.021134s
Dynamic (symbols) 16.000M (± 1.7%) i/s (62.50 ns/i) - 80.395M in 5.026229s

Comparison:
Static: 26137140.5 i/s
Dynamic (symbols): 16000110.8 i/s - 1.63x slower
Dynamic (strings): 11631159.4 i/s - 2.25x slower
....

=== scripts/numerics

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.1 + 0 }
benchmark.report("Rational") { (1 / 1r) + 0 }
benchmark.report("BigDecimal") { BigDecimal("0.1") + 0 }

benchmark.compare!
end
----
====

*Benchmark*

....
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
Integer 4.818M i/100ms
Float 4.688M i/100ms
Rational 1.440M i/100ms
BigDecimal 285.130k i/100ms
Calculating -------------------------------------
Integer 72.057M (± 2.9%) i/s (13.88 ns/i) - 361.327M in 5.019619s
Float 62.474M (± 0.7%) i/s (16.01 ns/i) - 314.066M in 5.027353s
Rational 15.063M (± 0.3%) i/s (66.39 ns/i) - 76.338M in 5.067855s
BigDecimal 2.871M (± 0.9%) i/s (348.31 ns/i) - 14.542M in 5.065332s

Comparison:
Integer: 72057483.7 i/s
Float: 62474330.5 i/s - 1.15x slower
Rational: 15063323.3 i/s - 4.78x slower
BigDecimal: 2871025.0 i/s - 25.10x slower
....

=== scripts/pattern_matching/multiple_type_check

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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("include?") { [String, Symbol].include? :test.class }
benchmark.report("in") { :test in String | Symbol }

benchmark.compare!
end
----
====

*Benchmark*

....
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
include? 3.306M i/100ms
in 2.232M i/100ms
Calculating -------------------------------------
include? 39.476M (± 0.2%) i/s (25.33 ns/i) - 198.332M in 5.024176s
in 26.164M (± 0.1%) i/s (38.22 ns/i) - 131.686M in 5.033138s

Comparison:
include?: 39475647.2 i/s
in: 26163792.0 i/s - 1.51x slower
....

=== scripts/pattern_matching/single_type_check

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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("is_a?") { :test.is_a? Symbol }
benchmark.report("in") { :test in Symbol }

benchmark.compare!
end
----
====

*Benchmark*

....
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
is_a? 4.392M i/100ms
in 3.021M i/100ms
Calculating -------------------------------------
is_a? 71.209M (± 0.2%) i/s (14.04 ns/i) - 360.148M in 5.057619s
in 38.621M (± 1.2%) i/s (25.89 ns/i) - 193.351M in 5.007114s

Comparison:
is_a?: 71209235.1 i/s
in: 38621042.8 i/s - 1.84x slower
....

=== scripts/refinements/import

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
With 48.000 i/100ms
Without 409.598k i/100ms
Calculating -------------------------------------
With 282.342 (± 0.4%) i/s (3.54 ms/i) - 1.440k in 5.100310s
Without 4.139M (± 1.7%) i/s (241.60 ns/i) - 20.889M in 5.048344s

Comparison:
Without: 4139064.0 i/s
With: 282.3 i/s - 14659.74x slower
....

=== scripts/refinements/initialize

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
With 1.462M i/100ms
Without 1.463M i/100ms
Calculating -------------------------------------
With 16.904M (± 0.9%) i/s (59.16 ns/i) - 84.773M in 5.015288s
Without 16.739M (± 1.1%) i/s (59.74 ns/i) - 84.878M in 5.071267s

Comparison:
With: 16904250.5 i/s
Without: 16739147.2 i/s - same-ish: difference falls within error
....

=== scripts/refinements/message

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
With 2.722M i/100ms
Without 4.843M i/100ms
Calculating -------------------------------------
With 37.130M (± 0.3%) i/s (26.93 ns/i) - 187.803M in 5.058008s
Without 68.976M (± 4.2%) i/s (14.50 ns/i) - 348.707M in 5.067107s

Comparison:
Without: 68975872.3 i/s
With: 37130284.9 i/s - 1.86x slower
....

=== scripts/refinements/refine

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
With 52.000 i/100ms
Without 402.935k i/100ms
Calculating -------------------------------------
With 284.492 (± 0.7%) i/s (3.52 ms/i) - 1.456k in 5.118210s
Without 4.116M (± 1.8%) i/s (242.94 ns/i) - 20.953M in 5.091996s

Comparison:
Without: 4116202.1 i/s
With: 284.5 i/s - 14468.62x slower
....

=== scripts/strings/concatenation

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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

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

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

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

# 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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
Implicit (<) 4.799M i/100ms
Implicit (>) 4.553M i/100ms
Interpolation (<) 1.758M i/100ms
Interpolation (>) 453.241k i/100ms
#+ (<) 1.306M i/100ms
#+ (>) 166.101k i/100ms
#concat (<) 523.312k i/100ms
#concat (>) 234.069k i/100ms
#<< (<) 556.835k i/100ms
#<< (>) 302.248k i/100ms
Array#join (<) 1.065M i/100ms
Array#join (>) 384.393k i/100ms
Calculating -------------------------------------
Implicit (<) 73.295M (± 0.1%) i/s (13.64 ns/i) - 369.501M in 5.041267s
Implicit (>) 73.267M (± 0.3%) i/s (13.65 ns/i) - 368.762M in 5.033156s
Interpolation (<) 18.250M (± 2.0%) i/s (54.79 ns/i) - 91.404M in 5.010379s
Interpolation (>) 4.844M (± 0.9%) i/s (206.44 ns/i) - 24.475M in 5.053113s
#+ (<) 13.587M (± 1.9%) i/s (73.60 ns/i) - 67.921M in 5.000883s
#+ (>) 1.544M (± 2.3%) i/s (647.82 ns/i) - 7.807M in 5.060098s
#concat (<) 5.635M (± 3.1%) i/s (177.45 ns/i) - 28.259M in 5.019545s
#concat (>) 2.375M (± 2.7%) i/s (421.10 ns/i) - 11.938M in 5.030584s
#<< (<) 5.943M (± 3.4%) i/s (168.27 ns/i) - 30.069M in 5.065748s
#<< (>) 3.099M (± 2.8%) i/s (322.66 ns/i) - 15.717M in 5.075307s
Array#join (<) 11.232M (± 0.9%) i/s (89.03 ns/i) - 56.421M in 5.023741s
Array#join (>) 4.172M (± 1.6%) i/s (239.70 ns/i) - 21.142M in 5.068889s

Comparison:
Implicit (<): 73295358.8 i/s
Implicit (>): 73267016.7 i/s - same-ish: difference falls within error
Interpolation (<): 18250066.2 i/s - 4.02x slower
#+ (<): 13586594.1 i/s - 5.39x slower
Array#join (<): 11231833.8 i/s - 6.53x slower
#<< (<): 5942722.8 i/s - 12.33x slower
#concat (<): 5635299.8 i/s - 13.01x slower
Interpolation (>): 4843906.9 i/s - 15.13x slower
Array#join (>): 4171886.7 i/s - 17.57x slower
#<< (>): 3099282.3 i/s - 23.65x slower
#concat (>): 2374759.3 i/s - 30.86x slower
#+ (>): 1543630.9 i/s - 47.48x slower
....

=== scripts/strings/matching

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
#match? 2.495M i/100ms
#=~ 1.446M i/100ms
#start_with? (String)
3.037M i/100ms
#start_with? (Regex) 806.851k i/100ms
#end_with? 3.167M i/100ms
Calculating -------------------------------------
#match? 29.221M (± 0.1%) i/s (34.22 ns/i) - 147.203M in 5.037510s
#=~ 15.667M (± 7.1%) i/s (63.83 ns/i) - 78.073M in 5.023256s
#start_with? (String)
35.530M (± 0.4%) i/s (28.14 ns/i) - 179.183M in 5.043172s
#start_with? (Regex) 7.438M (±24.5%) i/s (134.44 ns/i) - 34.695M in 5.023176s
#end_with? 36.134M (± 0.2%) i/s (27.68 ns/i) - 183.713M in 5.084305s

Comparison:
#end_with?: 36133533.7 i/s
#start_with? (String): 35530349.2 i/s - 1.02x slower
#match?: 29221455.8 i/s - 1.24x slower
#=~: 15666854.2 i/s - 2.31x slower
#start_with? (Regex): 7438366.7 i/s - 4.86x slower
....

=== scripts/strings/split

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
Without Block 16.000 i/100ms
With Block 14.000 i/100ms
Calculating -------------------------------------
Without Block 160.934 (± 1.9%) i/s (6.21 ms/i) - 816.000 in 5.071684s
With Block 147.259 (± 0.7%) i/s (6.79 ms/i) - 742.000 in 5.039222s

Comparison:
Without Block: 160.9 i/s
With Block: 147.3 i/s - 1.09x slower
....

=== scripts/strings/substrings

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
#sub (string) 720.839k i/100ms
#sub (regex) 486.003k i/100ms
#gsub (string) 504.617k i/100ms
#gsub (regex) 264.917k i/100ms
#tr 1.303M i/100ms
Calculating -------------------------------------
#sub (string) 7.516M (± 0.8%) i/s (133.05 ns/i) - 38.204M in 5.083576s
#sub (regex) 5.268M (± 0.7%) i/s (189.83 ns/i) - 26.730M in 5.074524s
#gsub (string) 5.380M (± 0.6%) i/s (185.89 ns/i) - 27.249M in 5.065536s
#gsub (regex) 2.822M (± 0.8%) i/s (354.38 ns/i) - 14.306M in 5.069954s
#tr 13.969M (± 0.5%) i/s (71.59 ns/i) - 70.343M in 5.035839s

Comparison:
#tr: 13968882.9 i/s
#sub (string): 7515708.4 i/s - 1.86x slower
#gsub (string): 5379557.6 i/s - 2.60x slower
#sub (regex): 5267767.1 i/s - 2.65x slower
#gsub (regex): 2821821.0 i/s - 4.95x slower
....

=== scripts/thens

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
standard 520.294k i/100ms
then 488.597k i/100ms
Calculating -------------------------------------
standard 5.408M (± 0.8%) i/s (184.93 ns/i) - 27.055M in 5.003534s
then 5.043M (± 0.7%) i/s (198.29 ns/i) - 25.407M in 5.038137s

Comparison:
standard: 5407583.0 i/s
then: 5043181.2 i/s - 1.07x slower
....

=== scripts/values/inheritance

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
Struct 538.804k i/100ms
Subclass 518.538k i/100ms
Calculating -------------------------------------
Struct 5.886M (± 1.3%) i/s (169.90 ns/i) - 29.634M in 5.035664s
Subclass 5.620M (± 1.0%) i/s (177.92 ns/i) - 28.520M in 5.074817s

Comparison:
Struct: 5885853.0 i/s
Subclass: 5620422.5 i/s - 1.05x slower
....

=== scripts/values/initialization

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
Data (positional) 353.466k i/100ms
Data (keyword) 350.442k i/100ms
Data (custom) 318.060k i/100ms
Struct (positional) 1.131M i/100ms
Struct (keyword) 362.009k i/100ms
Struct (custom) 352.839k i/100ms
OpenStruct 11.114k i/100ms
Dry Struct 124.525k i/100ms
Calculating -------------------------------------
Data (positional) 3.719M (± 1.1%) i/s (268.90 ns/i) - 18.734M in 5.038109s
Data (keyword) 3.874M (± 2.0%) i/s (258.16 ns/i) - 19.625M in 5.068537s
Data (custom) 3.358M (± 1.7%) i/s (297.78 ns/i) - 16.857M in 5.021195s
Struct (positional) 12.121M (± 1.8%) i/s (82.50 ns/i) - 61.063M in 5.039431s
Struct (keyword) 3.805M (± 3.9%) i/s (262.83 ns/i) - 19.186M in 5.050768s
Struct (custom) 3.676M (± 1.6%) i/s (272.02 ns/i) - 18.700M in 5.088300s
OpenStruct 109.497k (± 2.8%) i/s (9.13 μs/i) - 555.700k in 5.079350s
Dry Struct 1.306M (± 0.9%) i/s (765.65 ns/i) - 6.600M in 5.053588s

Comparison:
Struct (positional): 12121050.5 i/s
Data (keyword): 3873563.1 i/s - 3.13x slower
Struct (keyword): 3804720.3 i/s - 3.19x slower
Data (positional): 3718834.2 i/s - 3.26x slower
Struct (custom): 3676182.7 i/s - 3.30x slower
Data (custom): 3358180.2 i/s - 3.61x slower
Dry Struct: 1306074.7 i/s - 9.28x slower
OpenStruct: 109496.8 i/s - 110.70x 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*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
Data 4.138M i/100ms
Struct 4.402M i/100ms
OpenStruct 2.431M i/100ms
Dry Struct 3.723M i/100ms
Calculating -------------------------------------
Data 67.301M (± 2.9%) i/s (14.86 ns/i) - 339.284M in 5.046842s
Struct 67.277M (± 0.4%) i/s (14.86 ns/i) - 338.976M in 5.038558s
OpenStruct 31.752M (± 0.1%) i/s (31.49 ns/i) - 160.441M in 5.052954s
Dry Struct 46.533M (± 0.2%) i/s (21.49 ns/i) - 234.527M in 5.040063s

Comparison:
Data: 67301380.0 i/s
Struct: 67277416.8 i/s - same-ish: difference falls within error
Dry Struct: 46532814.0 i/s - 1.45x slower
OpenStruct: 31751956.0 i/s - 2.12x slower
....

=== scripts/values/writing

.*Source*
[%collapsible]
====
[source,ruby]
----
#! /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.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
Data 365.681k i/100ms
Struct 4.017M i/100ms
OpenStruct 1.970M i/100ms
Calculating -------------------------------------
Data 3.818M (± 1.0%) i/s (261.92 ns/i) - 19.381M in 5.076819s
Struct 53.012M (± 0.3%) i/s (18.86 ns/i) - 265.090M in 5.000654s
OpenStruct 24.920M (± 0.2%) i/s (40.13 ns/i) - 126.093M in 5.059884s

Comparison:
Struct: 53011585.3 i/s
OpenStruct: 24920314.7 i/s - 2.13x slower
Data: 3817956.9 i/s - 13.88x slower
....

== Development

To contribute, run:

[source,bash]
----
git clone https://github.com/bkuhlmann/benchmarks.git
cd benchmarks
bin/setup
----

To render documentation for all benchmark scripts, run:

[source,bash]
----
bin/render
----

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

== Tests

To test, run:

[source,bash]
----
bin/rake
----

== link:https://alchemists.io/policies/license[License]

== link:https://alchemists.io/policies/security[Security]

== link:https://alchemists.io/policies/code_of_conduct[Code of Conduct]

== link:https://alchemists.io/policies/contributions[Contributions]

== link:https://alchemists.io/policies/developer_certificate_of_origin[Developer Certificate of Origin]

== link:https://alchemists.io/projects/benchmarks/versions[Versions]

== link:https://alchemists.io/community[Community]

== Credits

* Built with link:https://alchemists.io/projects/rubysmith[Rubysmith].
* Engineered by link:https://alchemists.io/team/brooke_kuhlmann[Brooke Kuhlmann].