Last active
June 12, 2026 05:16
-
-
Save shugo/07e62c44bc4765ecff6d2b8e704b5f38 to your computer and use it in GitHub Desktop.
Proc#with_refinements benchmark
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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))) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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