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.
- Features
- Requirements
- Setup
- Usage
- scripts/arrays/concatenation
- scripts/arrays/search
- scripts/bindings
- scripts/constants/equality
- scripts/constants/lookup
- scripts/delegates
- scripts/functions/blocks
- scripts/functions/types
- scripts/hashes/lookup
- scripts/hashes/merge
- scripts/hashes/reduce
- scripts/loops
- scripts/methods/define_method
- scripts/methods/method_proc
- scripts/methods/scoped_send
- scripts/methods/send
- scripts/numerics
- scripts/pattern_matching/multiple_type_check
- scripts/pattern_matching/single_type_check
- scripts/refinements/import
- scripts/refinements/initialize
- scripts/refinements/message
- scripts/refinements/refine
- scripts/strings/concatenation
- scripts/strings/matching
- scripts/strings/split
- scripts/strings/substrings
- scripts/thens
- scripts/values/inheritance
- scripts/values/initialization
- scripts/values/reading
- scripts/values/writing
- Development
- Tests
- License
- Security
- Code of Conduct
- Contributions
- Developer Certificate of Origin
- Versions
- Community
- Credits
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 6.4.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
#! /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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
#+ 2.041M i/100ms
#+= 920.566k i/100ms
#concat 763.628k i/100ms
#| 510.613k i/100ms
#<< + #flatten 221.842k i/100ms
splat + #flatten 241.441k i/100ms
multi-splat 1.086M i/100ms
Calculating -------------------------------------
#+ 22.794M (± 1.8%) i/s (43.87 ns/i) - 114.323M in 5.017221s
#+= 9.402M (± 3.9%) i/s (106.36 ns/i) - 47.869M in 5.099447s
#concat 8.085M (± 2.7%) i/s (123.69 ns/i) - 40.472M in 5.009540s
#| 5.224M (± 2.0%) i/s (191.43 ns/i) - 26.552M in 5.085088s
#<< + #flatten 2.372M (± 4.8%) i/s (421.59 ns/i) - 11.979M in 5.062112s
splat + #flatten 2.447M (± 5.0%) i/s (408.69 ns/i) - 12.313M in 5.044622s
multi-splat 11.437M (± 3.0%) i/s (87.43 ns/i) - 57.535M in 5.034896s
Comparison:
#+: 22793834.0 i/s
multi-splat: 11437362.6 i/s - 1.99x slower
#+=: 9401673.8 i/s - 2.42x slower
#concat: 8085032.5 i/s - 2.82x slower
#|: 5223801.3 i/s - 4.36x slower
splat + #flatten: 2446861.9 i/s - 9.32x slower
#<< + #flatten: 2371963.7 i/s - 9.61x 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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
#grep 175.145k i/100ms
#select 236.877k i/100ms
Calculating -------------------------------------
#grep 1.794M (± 4.6%) i/s (557.47 ns/i) - 9.108M in 5.088368s
#select 2.448M (± 3.8%) i/s (408.47 ns/i) - 12.318M in 5.038546s
Comparison:
#select: 2448151.9 i/s
#grep: 1793833.0 i/s - 1.36x slower
scripts/bindings
Source
#! /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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
Binding 709.213k i/100ms
Pinning 1.663M i/100ms
Calculating -------------------------------------
Binding 7.671M (± 5.5%) i/s (130.37 ns/i) - 38.298M in 5.006932s
Pinning 18.090M (± 6.4%) i/s (55.28 ns/i) - 91.457M in 5.077294s
Comparison:
Pinning: 18090003.1 i/s
Binding: 7670731.9 i/s - 2.36x slower
scripts/constants/equality
Source
#! /usr/bin/env ruby
# frozen_string_literal: true
require "bundler/inline"
gemfile true do
source "https://rubygems.org"
gem "benchmark-ips"
end
TEST = "test"
Benchmark.ips do |benchmark|
benchmark.config time: 5, warmup: 2
benchmark.report("Constant First") { TEST == "test" }
benchmark.report("Value First") { "test" == TEST }
benchmark.compare!
end
Benchmark
ruby 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
Constant First 4.277M i/100ms
Value First 4.309M i/100ms
Calculating -------------------------------------
Constant First 55.538M (± 0.2%) i/s (18.01 ns/i) - 278.012M in 5.005822s
Value First 55.303M (± 1.9%) i/s (18.08 ns/i) - 280.063M in 5.066325s
Comparison:
Constant First: 55537887.6 i/s
Value First: 55302727.6 i/s - same-ish: difference falls within error
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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
#[] 2.541M i/100ms
Module.get (symbol) 3.355M i/100ms
Module.get (string) 1.649M i/100ms
Object.get 999.683k i/100ms
Calculating -------------------------------------
#[] 30.883M (± 0.4%) i/s (32.38 ns/i) - 155.018M in 5.019618s
Module.get (symbol) 40.391M (± 0.3%) i/s (24.76 ns/i) - 204.660M in 5.067003s
Module.get (string) 18.154M (± 0.6%) i/s (55.08 ns/i) - 92.351M in 5.087198s
Object.get 10.796M (± 0.5%) i/s (92.63 ns/i) - 53.983M in 5.000510s
Comparison:
Module.get (symbol): 40391128.3 i/s
#[]: 30882959.9 i/s - 1.31x slower
Module.get (string): 18154323.3 i/s - 2.22x slower
Object.get: 10795788.7 i/s - 3.74x 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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
Forward 2.154M i/100ms
Delegate 1.730M i/100ms
Simple Delegator 455.864k i/100ms
Delegate Class 454.933k i/100ms
Calculating -------------------------------------
Forward 25.482M (± 0.2%) i/s (39.24 ns/i) - 129.237M in 5.071790s
Delegate 21.359M (± 1.0%) i/s (46.82 ns/i) - 107.246M in 5.021639s
Simple Delegator 5.139M (± 3.5%) i/s (194.60 ns/i) - 25.984M in 5.062787s
Delegate Class 5.153M (± 4.2%) i/s (194.04 ns/i) - 25.931M in 5.041101s
Comparison:
Forward: 25481756.8 i/s
Delegate: 21359007.4 i/s - 1.19x slower
Delegate Class: 5153461.2 i/s - 4.94x slower
Simple Delegator: 5138633.7 i/s - 4.96x slower
scripts/functions/blocks
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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
Block (implicit) 4.377M i/100ms
Block (implicit guard)
4.307M i/100ms
Block (explicit) 765.699k i/100ms
Block (explicit guard)
733.839k i/100ms
Lambda 2.591M i/100ms
Proc 2.759M i/100ms
Calculating -------------------------------------
Block (implicit) 55.054M (± 0.4%) i/s (18.16 ns/i) - 275.761M in 5.009019s
Block (implicit guard)
55.424M (± 0.2%) i/s (18.04 ns/i) - 279.958M in 5.051249s
Block (explicit) 8.617M (±10.7%) i/s (116.05 ns/i) - 42.879M in 5.028149s
Block (explicit guard)
8.640M (±11.0%) i/s (115.74 ns/i) - 43.297M in 5.067570s
Lambda 34.272M (± 0.2%) i/s (29.18 ns/i) - 173.607M in 5.065559s
Proc 34.493M (± 0.2%) i/s (28.99 ns/i) - 173.797M in 5.038588s
Comparison:
Block (implicit guard): 55423617.9 i/s
Block (implicit): 55053980.1 i/s - 1.01x slower
Proc: 34493418.9 i/s - 1.61x slower
Lambda: 34272211.4 i/s - 1.62x slower
Block (explicit guard): 8640102.7 i/s - 6.41x slower
Block (explicit): 8616900.9 i/s - 6.43x slower
scripts/functions/types
Source
#! /usr/bin/env ruby
# frozen_string_literal: true
require "bundler/inline"
gemfile true do
source "https://rubygems.org"
gem "benchmark-ips"
end
ProcFunction = proc { |message = "message"| message }
LambdaFunction = -> message = "benchmark" { message }
module ModuleFunction
module_function
def call(message = "benchmark") = message
end
class ClassFunction
def initialize message = "benchmark"
@message = message
end
def call = message
private
attr_reader :message
end
memoized_function = ClassFunction.new
Benchmark.ips do |benchmark|
benchmark.config time: 5, warmup: 2
benchmark.report("Proc") { ProcFunction.call }
benchmark.report("Lambda") { LambdaFunction.call }
benchmark.report("Module") { ModuleFunction.call }
benchmark.report("Class (new)") { ClassFunction.new.call }
benchmark.report("Class (memoized)") { memoized_function.call }
benchmark.compare!
end
Benchmark
ruby 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
Proc 2.215M i/100ms
Lambda 2.228M i/100ms
Module 4.451M i/100ms
Class (new) 2.504M i/100ms
Class (memoized) 4.430M i/100ms
Calculating -------------------------------------
Proc 24.163M (± 1.4%) i/s (41.38 ns/i) - 121.842M in 5.043510s
Lambda 24.785M (± 1.2%) i/s (40.35 ns/i) - 124.746M in 5.033908s
Module 59.937M (± 0.9%) i/s (16.68 ns/i) - 302.661M in 5.050088s
Class (new) 28.137M (± 2.4%) i/s (35.54 ns/i) - 142.739M in 5.075867s
Class (memoized) 55.985M (± 0.9%) i/s (17.86 ns/i) - 283.501M in 5.064328s
Comparison:
Module: 59937028.6 i/s
Class (memoized): 55984529.3 i/s - 1.07x slower
Class (new): 28137026.3 i/s - 2.13x slower
Lambda: 24785195.0 i/s - 2.42x slower
Proc: 24163375.2 i/s - 2.48x 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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
#[] 4.091M i/100ms
#fetch 3.558M i/100ms
#fetch (default) 3.537M i/100ms
#fetch (block) 3.528M i/100ms
#dig 3.896M i/100ms
Calculating -------------------------------------
#[] 51.599M (± 0.4%) i/s (19.38 ns/i) - 261.851M in 5.074852s
#fetch 42.667M (± 0.4%) i/s (23.44 ns/i) - 213.486M in 5.003628s
#fetch (default) 42.271M (± 0.6%) i/s (23.66 ns/i) - 212.206M in 5.020335s
#fetch (block) 42.642M (± 0.3%) i/s (23.45 ns/i) - 215.197M in 5.046671s
#dig 48.266M (± 0.6%) i/s (20.72 ns/i) - 241.570M in 5.005108s
Comparison:
#[]: 51598749.0 i/s
#dig: 48266387.2 i/s - 1.07x slower
#fetch: 42666996.7 i/s - 1.21x slower
#fetch (block): 42641658.5 i/s - 1.21x slower
#fetch (default): 42270877.3 i/s - 1.22x 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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
Splat 1.338M i/100ms
Merge 951.857k i/100ms
Merge! 1.165M i/100ms
Dup Merge! 641.603k i/100ms
Calculating -------------------------------------
Splat 14.253M (± 5.1%) i/s (70.16 ns/i) - 72.227M in 5.080706s
Merge 9.126M (± 4.3%) i/s (109.58 ns/i) - 45.689M in 5.015660s
Merge! 12.741M (± 4.1%) i/s (78.49 ns/i) - 64.086M in 5.038507s
Dup Merge! 6.901M (± 4.9%) i/s (144.90 ns/i) - 34.647M in 5.032388s
Comparison:
Splat: 14252857.3 i/s
Merge!: 12741219.4 i/s - 1.12x slower
Merge: 9125612.2 i/s - 1.56x slower
Dup Merge!: 6901442.6 i/s - 2.07x 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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
Reduce 60.829k i/100ms
With Object 112.893k i/100ms
Calculating -------------------------------------
Reduce 613.452k (± 1.4%) i/s (1.63 μs/i) - 3.102M in 5.058093s
With Object 1.172M (± 4.9%) i/s (853.10 ns/i) - 5.870M in 5.020591s
Comparison:
With Object: 1172192.0 i/s
Reduce: 613451.9 i/s - 1.91x 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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
for 18.540k i/100ms
#each 20.918k i/100ms
Calculating -------------------------------------
for 188.671k (± 2.4%) i/s (5.30 μs/i) - 945.540k in 5.014384s
#each 210.097k (± 0.1%) i/s (4.76 μs/i) - 1.067M in 5.077744s
Comparison:
#each: 210097.2 i/s
for: 188670.5 i/s - 1.11x 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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
Wrapped 1.513M i/100ms
Defined 1.452M i/100ms
Delegated 1.057M i/100ms
Calculating -------------------------------------
Wrapped 16.392M (± 2.2%) i/s (61.00 ns/i) - 83.190M in 5.077430s
Defined 16.887M (± 3.4%) i/s (59.22 ns/i) - 85.679M in 5.079576s
Delegated 11.425M (± 2.3%) i/s (87.53 ns/i) - 58.122M in 5.090109s
Comparison:
Defined: 16887107.4 i/s
Wrapped: 16392341.2 i/s - same-ish: difference falls within error
Delegated: 11425005.5 i/s - 1.48x 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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
Direct (s) 1.639M i/100ms
Direct (m) 593.203k i/100ms
Proc (s) 796.563k i/100ms
Proc (m) 286.583k i/100ms
Method To Proc (s) 341.162k i/100ms
Method To Proc (m) 219.307k i/100ms
Calculating -------------------------------------
Direct (s) 17.826M (± 4.3%) i/s (56.10 ns/i) - 90.123M in 5.065128s
Direct (m) 6.082M (± 2.4%) i/s (164.41 ns/i) - 30.847M in 5.074261s
Proc (s) 9.032M (± 2.0%) i/s (110.72 ns/i) - 45.404M in 5.028831s
Proc (m) 2.991M (± 2.5%) i/s (334.33 ns/i) - 15.189M in 5.081408s
Method To Proc (s) 3.705M (± 7.5%) i/s (269.92 ns/i) - 18.423M in 5.000012s
Method To Proc (m) 2.244M (± 5.9%) i/s (445.60 ns/i) - 11.185M in 5.000913s
Comparison:
Direct (s): 17826000.7 i/s
Proc (s): 9032188.7 i/s - 1.97x slower
Direct (m): 6082449.7 i/s - 2.93x slower
Method To Proc (s): 3704747.0 i/s - 4.81x slower
Proc (m): 2991088.4 i/s - 5.96x slower
Method To Proc (m): 2244146.0 i/s - 7.94x slower
scripts/methods/scoped_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 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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
Public 1.972M i/100ms
Private 4.431M i/100ms
Calculating -------------------------------------
Public 22.708M (± 1.7%) i/s (44.04 ns/i) - 114.397M in 5.039230s
Private 56.018M (±14.3%) i/s (17.85 ns/i) - 265.872M in 5.054211s
Comparison:
Private: 56018442.7 i/s
Public: 22707682.3 i/s - 2.47x 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
benchmark.report("Static") { Static.call }
benchmark.report("Dynamic (strings)") { Dynamic.with_strings }
benchmark.report("Dynamic (symbols)") { Dynamic.with_symbols }
benchmark.compare!
end
Benchmark
ruby 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
Static 2.450M i/100ms
Dynamic (strings) 950.975k i/100ms
Dynamic (symbols) 1.212M i/100ms
Calculating -------------------------------------
Static 27.059M (± 3.1%) i/s (36.96 ns/i) - 137.182M in 5.074840s
Dynamic (strings) 11.243M (± 0.8%) i/s (88.94 ns/i) - 57.058M in 5.075304s
Dynamic (symbols) 15.294M (± 1.3%) i/s (65.39 ns/i) - 77.579M in 5.073369s
Comparison:
Static: 27058712.0 i/s
Dynamic (symbols): 15293984.0 i/s - 1.77x slower
Dynamic (strings): 11243136.2 i/s - 2.41x 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.1 + 0 }
benchmark.report("Rational") { (1 / 1r) + 0 }
benchmark.report("BigDecimal") { BigDecimal("0.1") + 0 }
benchmark.compare!
end
Benchmark
ruby 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
Integer 5.201M i/100ms
Float 4.672M i/100ms
Rational 1.511M i/100ms
BigDecimal 392.886k i/100ms
Calculating -------------------------------------
Integer 69.051M (± 2.1%) i/s (14.48 ns/i) - 348.490M in 5.049610s
Float 60.799M (± 0.2%) i/s (16.45 ns/i) - 308.361M in 5.071791s
Rational 16.517M (± 2.9%) i/s (60.54 ns/i) - 83.094M in 5.035595s
BigDecimal 4.020M (± 7.6%) i/s (248.76 ns/i) - 20.037M in 5.011139s
Comparison:
Integer: 69051130.2 i/s
Float: 60799372.8 i/s - 1.14x slower
Rational: 16517265.0 i/s - 4.18x slower
BigDecimal: 4019908.7 i/s - 17.18x slower
scripts/pattern_matching/multiple_type_check
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("include?") { [String, Symbol].include? :test.class }
benchmark.report("in") { :test in String | Symbol }
benchmark.compare!
end
Benchmark
ruby 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
include? 3.026M i/100ms
in 2.265M i/100ms
Calculating -------------------------------------
include? 36.895M (± 1.6%) i/s (27.10 ns/i) - 184.582M in 5.004292s
in 26.819M (± 1.1%) i/s (37.29 ns/i) - 135.894M in 5.067758s
Comparison:
include?: 36894579.7 i/s
in: 26819158.1 i/s - 1.38x slower
scripts/pattern_matching/single_type_check
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("is_a?") { :test.is_a? Symbol }
benchmark.report("in") { :test in Symbol }
benchmark.compare!
end
Benchmark
ruby 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
is_a? 5.043M i/100ms
in 3.279M i/100ms
Calculating -------------------------------------
is_a? 68.582M (± 0.4%) i/s (14.58 ns/i) - 342.936M in 5.000442s
in 38.724M (± 1.2%) i/s (25.82 ns/i) - 196.724M in 5.080921s
Comparison:
is_a?: 68582153.6 i/s
in: 38724445.4 i/s - 1.77x 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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
With 361.000 i/100ms
Without 265.247k i/100ms
Calculating -------------------------------------
With 5.399k (±193.0%) i/s (185.22 μs/i) - 10.469k in 5.385643s
Without 2.616M (± 6.0%) i/s (382.20 ns/i) - 13.262M in 5.090719s
Comparison:
Without: 2616438.1 i/s
With: 5399.0 i/s - 484.62x 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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
With 2.610M i/100ms
Without 2.478M i/100ms
Calculating -------------------------------------
With 30.900M (± 2.1%) i/s (32.36 ns/i) - 156.573M in 5.069302s
Without 30.128M (± 2.8%) i/s (33.19 ns/i) - 151.164M in 5.021253s
Comparison:
With: 30899772.3 i/s
Without: 30127903.2 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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
With 3.057M i/100ms
Without 4.933M i/100ms
Calculating -------------------------------------
With 36.283M (± 0.5%) i/s (27.56 ns/i) - 183.444M in 5.056028s
Without 64.881M (± 2.9%) i/s (15.41 ns/i) - 325.610M in 5.023843s
Comparison:
Without: 64881052.6 i/s
With: 36283337.8 i/s - 1.79x 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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
With 364.000 i/100ms
Without 267.270k i/100ms
Calculating -------------------------------------
With 5.265k (±190.8%) i/s (189.93 μs/i) - 10.920k in 5.273126s
Without 2.578M (± 2.1%) i/s (387.97 ns/i) - 13.096M in 5.083279s
Comparison:
Without: 2577506.5 i/s
With: 5265.0 i/s - 489.55x 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
# 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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
Implicit (<) 4.612M i/100ms
Implicit (>) 5.156M i/100ms
Interpolation (<) 1.681M i/100ms
Interpolation (>) 472.381k i/100ms
#+ (<) 1.279M i/100ms
#+ (>) 161.656k i/100ms
#concat (<) 524.718k i/100ms
#concat (>) 228.594k i/100ms
#<< (<) 576.251k i/100ms
#<< (>) 302.123k i/100ms
Array#join (<) 1.129M i/100ms
Array#join (>) 397.504k i/100ms
Calculating -------------------------------------
Implicit (<) 71.067M (± 0.1%) i/s (14.07 ns/i) - 359.707M in 5.061526s
Implicit (>) 71.055M (± 0.2%) i/s (14.07 ns/i) - 355.735M in 5.006479s
Interpolation (<) 18.556M (± 4.3%) i/s (53.89 ns/i) - 94.139M in 5.083215s
Interpolation (>) 5.022M (± 2.0%) i/s (199.11 ns/i) - 25.509M in 5.080934s
#+ (<) 13.447M (± 2.3%) i/s (74.37 ns/i) - 67.761M in 5.041852s
#+ (>) 1.615M (± 1.0%) i/s (619.16 ns/i) - 8.083M in 5.005058s
#concat (<) 5.668M (± 7.9%) i/s (176.44 ns/i) - 28.335M in 5.027373s
#concat (>) 2.535M (± 7.1%) i/s (394.52 ns/i) - 12.801M in 5.072887s
#<< (<) 6.124M (± 5.1%) i/s (163.30 ns/i) - 31.118M in 5.093028s
#<< (>) 3.316M (±11.8%) i/s (301.60 ns/i) - 16.617M in 5.081439s
Array#join (<) 11.789M (± 3.1%) i/s (84.82 ns/i) - 59.820M in 5.078832s
Array#join (>) 4.197M (± 1.6%) i/s (238.26 ns/i) - 21.068M in 5.020997s
Comparison:
Implicit (<): 71067132.6 i/s
Implicit (>): 71055061.6 i/s - same-ish: difference falls within error
Interpolation (<): 18555791.4 i/s - 3.83x slower
#+ (<): 13447051.5 i/s - 5.28x slower
Array#join (<): 11789148.3 i/s - 6.03x slower
#<< (<): 6123868.9 i/s - 11.60x slower
#concat (<): 5667784.5 i/s - 12.54x slower
Interpolation (>): 5022411.6 i/s - 14.15x slower
Array#join (>): 4197045.1 i/s - 16.93x slower
#<< (>): 3315687.6 i/s - 21.43x slower
#concat (>): 2534713.1 i/s - 28.04x slower
#+ (>): 1615100.5 i/s - 44.00x 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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
#match? 2.526M i/100ms
#=~ 1.339M i/100ms
#start_with? (String)
3.002M i/100ms
#start_with? (Regex) 1.048M i/100ms
#end_with? 3.031M i/100ms
Calculating -------------------------------------
#match? 28.780M (± 0.1%) i/s (34.75 ns/i) - 143.989M in 5.003006s
#=~ 15.506M (± 0.3%) i/s (64.49 ns/i) - 77.686M in 5.010257s
#start_with? (String)
34.909M (± 0.1%) i/s (28.65 ns/i) - 177.099M in 5.073225s
#start_with? (Regex) 11.312M (± 2.4%) i/s (88.41 ns/i) - 56.611M in 5.007621s
#end_with? 35.429M (± 0.1%) i/s (28.23 ns/i) - 178.822M in 5.047302s
Comparison:
#end_with?: 35429317.7 i/s
#start_with? (String): 34908613.0 i/s - 1.01x slower
#match?: 28780485.2 i/s - 1.23x slower
#=~: 15505538.4 i/s - 2.28x slower
#start_with? (Regex): 11311568.1 i/s - 3.13x 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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
Without Block 14.000 i/100ms
With Block 14.000 i/100ms
Calculating -------------------------------------
Without Block 146.507 (± 0.7%) i/s (6.83 ms/i) - 742.000 in 5.064948s
With Block 147.138 (± 0.7%) i/s (6.80 ms/i) - 742.000 in 5.043408s
Comparison:
With Block: 147.1 i/s
Without Block: 146.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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
#sub (string) 773.049k i/100ms
#sub (regex) 543.605k i/100ms
#gsub (string) 529.823k i/100ms
#gsub (regex) 303.931k i/100ms
#tr 1.303M i/100ms
Calculating -------------------------------------
#sub (string) 8.034M (± 3.1%) i/s (124.47 ns/i) - 40.199M in 5.008197s
#sub (regex) 5.754M (± 2.3%) i/s (173.81 ns/i) - 28.811M in 5.010357s
#gsub (string) 5.589M (± 1.4%) i/s (178.93 ns/i) - 28.081M in 5.025528s
#gsub (regex) 3.195M (± 1.7%) i/s (313.01 ns/i) - 16.108M in 5.043536s
#tr 14.137M (± 1.7%) i/s (70.74 ns/i) - 71.667M in 5.071218s
Comparison:
#tr: 14136555.5 i/s
#sub (string): 8033950.8 i/s - 1.76x slower
#sub (regex): 5753543.8 i/s - 2.46x slower
#gsub (string): 5588726.1 i/s - 2.53x slower
#gsub (regex): 3194812.0 i/s - 4.42x 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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
standard 518.819k i/100ms
then 473.521k i/100ms
Calculating -------------------------------------
standard 5.357M (± 1.6%) i/s (186.69 ns/i) - 26.979M in 5.037822s
then 4.990M (± 2.7%) i/s (200.40 ns/i) - 25.097M in 5.032935s
Comparison:
standard: 5356571.3 i/s
then: 4990075.4 i/s - 1.07x slower
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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
Struct 558.663k i/100ms
Subclass 529.001k i/100ms
Calculating -------------------------------------
Struct 5.979M (± 2.0%) i/s (167.25 ns/i) - 30.168M in 5.047641s
Subclass 5.465M (± 2.1%) i/s (182.99 ns/i) - 27.508M in 5.035777s
Comparison:
Struct: 5979019.4 i/s
Subclass: 5464846.9 i/s - 1.09x slower
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"
gem "ostruct"
end
Warning[:performance] = false
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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
Data (positional) 347.645k i/100ms
Data (keyword) 362.798k i/100ms
Data (custom) 305.941k i/100ms
Struct (positional) 1.134M i/100ms
Struct (keyword) 352.578k i/100ms
Struct (custom) 403.416k i/100ms
OpenStruct 8.106k i/100ms
Dry Struct 127.913k i/100ms
Calculating -------------------------------------
Data (positional) 3.695M (± 2.2%) i/s (270.64 ns/i) - 18.773M in 5.083364s
Data (keyword) 3.790M (± 2.7%) i/s (263.83 ns/i) - 19.228M in 5.076899s
Data (custom) 3.181M (± 4.4%) i/s (314.39 ns/i) - 15.909M in 5.011290s
Struct (positional) 12.671M (± 3.2%) i/s (78.92 ns/i) - 63.494M in 5.016454s
Struct (keyword) 3.691M (± 2.6%) i/s (270.94 ns/i) - 18.687M in 5.066576s
Struct (custom) 4.115M (± 3.9%) i/s (243.03 ns/i) - 20.574M in 5.008155s
OpenStruct 98.977k (±23.9%) i/s (10.10 μs/i) - 478.254k in 5.053046s
Dry Struct 1.305M (± 4.8%) i/s (766.10 ns/i) - 6.524M in 5.009218s
Comparison:
Struct (positional): 12670762.4 i/s
Struct (custom): 4114733.4 i/s - 3.08x slower
Data (keyword): 3790379.2 i/s - 3.34x slower
Data (positional): 3694910.1 i/s - 3.43x slower
Struct (keyword): 3690814.1 i/s - 3.43x slower
Data (custom): 3180762.4 i/s - 3.98x slower
Dry Struct: 1305310.7 i/s - 9.71x slower
OpenStruct: 98977.4 i/s - 128.02x 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"
gem "ostruct"
end
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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
Data 4.996M i/100ms
Struct 4.956M i/100ms
OpenStruct 2.695M i/100ms
Dry Struct 3.801M i/100ms
Calculating -------------------------------------
Data 65.623M (± 3.1%) i/s (15.24 ns/i) - 329.765M in 5.031036s
Struct 65.816M (± 0.5%) i/s (15.19 ns/i) - 332.049M in 5.045233s
OpenStruct 31.489M (± 0.1%) i/s (31.76 ns/i) - 159.018M in 5.049951s
Dry Struct 46.498M (± 0.2%) i/s (21.51 ns/i) - 235.678M in 5.068601s
Comparison:
Struct: 65816109.5 i/s
Data: 65623017.2 i/s - same-ish: difference falls within error
Dry Struct: 46497703.7 i/s - 1.42x slower
OpenStruct: 31489086.6 i/s - 2.09x 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"
gem "ostruct"
end
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 4.0.0 (2025-12-25 revision 553f1675f3) +YJIT +PRISM [arm64-darwin25.2.0]
Warming up --------------------------------------
Data 362.337k i/100ms
Struct 4.119M i/100ms
OpenStruct 2.193M i/100ms
Calculating -------------------------------------
Data 3.656M (± 4.5%) i/s (273.55 ns/i) - 18.479M in 5.065731s
Struct 51.295M (± 0.2%) i/s (19.50 ns/i) - 259.477M in 5.058586s
OpenStruct 25.689M (± 1.2%) i/s (38.93 ns/i) - 129.410M in 5.038280s
Comparison:
Struct: 51294551.5 i/s
OpenStruct: 25689237.2 i/s - 2.00x slower
Data: 3655574.7 i/s - 14.03x 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
-
Built with Rubysmith.
-
Engineered by Brooke Kuhlmann.