Skip to content

Instantly share code, notes, and snippets.

@shugo
Last active June 12, 2026 05:16
Show Gist options
  • Select an option

  • Save shugo/07e62c44bc4765ecff6d2b8e704b5f38 to your computer and use it in GitHub Desktop.

Select an option

Save shugo/07e62c44bc4765ecff6d2b8e704b5f38 to your computer and use it in GitHub Desktop.
Proc#with_refinements benchmark
require "objspace"
module M
refine String do
def shout = upcase + "!"
end
end
module A
refine(String) { def shout = "A" }
end
module B
refine(String) { def shout = "B" }
end
N = 10_000
BLOCK_SRC = <<~RUBY
->(items) {
total = 0
out = []
items.each do |item|
name, price = item
label = name.to_s.strip
next if label.empty?
total += price
out << "\#{label}: \#{price}"
end
cheap = items.select { |_, price| price < 40 }.size
summary = out.join(", ")
if total > 100
"\#{summary} (total: \#{total}, high, \#{cheap} cheap)"
else
"\#{summary} (total: \#{total}, \#{cheap} cheap)"
end
}
RUBY
# N distinct block literals, so each with_refinements copies a fresh iseq tree
def make_blocks(n)
eval("[" + ([BLOCK_SRC.chomp] * n).join(",\n") + "]")
end
def bench(label)
GC.start
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
yield
t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
printf("%-50s %8.1f ms (%.2f us/block)\n", label, (t1 - t0) * 1000, (t1 - t0) / N * 1e6)
end
def iseq_tree_size(iseqw)
size = ObjectSpace.memsize_of(iseqw)
iseqw.each_child { |c| size += iseq_tree_size(c) }
size
end
arg = [["apple", 50], ["banana", 30], [" ", 0], ["cherry", 40]]
blocks = make_blocks(N)
refined = nil
bench("call #{N} original blocks") { blocks.each { |b| b.call(arg) } }
bench("with_refinements x#{N} (first time: copy)") { refined = blocks.map { |b| b.with_refinements(M) } }
bench("call #{N} refined blocks") { refined.each { |b| b.call(arg) } }
bench("with_refinements x#{N} (memoized)") { blocks.each { |b| b.with_refinements(M) } }
bench("with_refinements x#{N}, alternating A/B") { blocks.each_with_index { |b, i| b.with_refinements(i.even? ? A : B) } }
o = blocks.first
r = o.with_refinements(M)
printf("iseq tree size: original %d bytes, copy %d bytes\n",
iseq_tree_size(RubyVM::InstructionSequence.of(o)),
iseq_tree_size(RubyVM::InstructionSequence.of(r)))
call 10000 original blocks 27.2 ms (2.72 us/block)
with_refinements x10000 (first time: copy) 261.8 ms (26.18 us/block)
call 10000 refined blocks 27.6 ms (2.76 us/block)
with_refinements x10000 (memoized) 3.2 ms (0.32 us/block)
with_refinements x10000, alternating A/B 251.8 ms (25.18 us/block)
iseq tree size: original 4040 bytes, copy 3992 bytes
call 10000 original blocks 152.0 ms (15.20 us/block)
with_refinements x10000 (first time: copy) 503.2 ms (50.32 us/block)
call 10000 refined blocks 57.3 ms (5.73 us/block)
with_refinements x10000 (memoized) 8.7 ms (0.87 us/block)
with_refinements x10000, alternating A/B 427.1 ms (42.71 us/block)
NameError: uninitialized constant RubyVM
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment