Last active
June 19, 2026 04:07
-
-
Save shugo/b58640e4b6c6df76f857159d79a5eb74 to your computer and use it in GitHub Desktop.
Benchmark of Proc#with_refinements in multi-Ractor mode
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
| module M | |
| refine String do | |
| def shout = upcase + "!" | |
| end | |
| end | |
| N = 2_000_000 | |
| prc = ->(s) { s } | |
| # Warm up the memo | |
| prc.with_refinements(M) | |
| # Baseline: plain proc call | |
| t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC) | |
| N.times { prc.call("x") } | |
| t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC) | |
| baseline = (t1 - t0) / N * 1e9 | |
| # Phase 1: Single-Ractor mode (rb_multi_ractor_p() == false) | |
| # Memo hit path only | |
| times_single = [] | |
| 3.times do | |
| t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC) | |
| N.times { prc.with_refinements(M) } | |
| t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC) | |
| times_single << (t1 - t0) / N * 1e9 | |
| end | |
| single = times_single.min | |
| # Create a Ractor to flip multi-Ractor mode on permanently | |
| r = Ractor.new { true } | |
| r.value | |
| # Phase 2: Multi-Ractor mode, no contention (main Ractor only) | |
| times_multi = [] | |
| 3.times do | |
| t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC) | |
| N.times { prc.with_refinements(M) } | |
| t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC) | |
| times_multi << (t1 - t0) / N * 1e9 | |
| end | |
| multi = times_multi.min | |
| puts "=== RB_VM_LOCKING() overhead on with_refinements memo hit ===" | |
| puts | |
| printf(" Proc#call (baseline): %6.1f ns/call\n", baseline) | |
| printf(" with_refinements (1 Ractor): %6.1f ns/call\n", single) | |
| printf(" with_refinements (multi): %6.1f ns/call\n", multi) | |
| printf(" Lock overhead (no contention):%6.1f ns/call (%.2fx)\n", | |
| multi - single, multi / single) |
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 | |
| # Measure per-memo overhead precisely using a single proc | |
| prc = ->(s) { s.shout } | |
| GC.start; GC.start; GC.start | |
| before = GC.stat(:heap_live_slots) | |
| refined = prc.with_refinements(M) | |
| GC.start; GC.start; GC.start | |
| after = GC.stat(:heap_live_slots) | |
| printf("Single with_refinements:\n") | |
| printf(" New live objects: +%d\n", after - before) | |
| # Measure the T_DATA/struct size directly | |
| memo_iseq = RubyVM::InstructionSequence.of(prc) | |
| printf(" iseq memsize (original prc): %d bytes\n", ObjectSpace.memsize_of(memo_iseq)) | |
| refined_iseq = RubyVM::InstructionSequence.of(refined) | |
| printf(" iseq memsize (refined prc): %d bytes\n", ObjectSpace.memsize_of(refined_iseq)) | |
| # Measure memo replacement (alternating modules) | |
| prc2 = ->(s) { s.shout } | |
| prc2.with_refinements(M) # warm | |
| GC.start; GC.start; GC.start | |
| before2 = GC.stat(:heap_live_slots) | |
| prc2.with_refinements(A) # replace memo | |
| GC.start; GC.start; GC.start | |
| after2 = GC.stat(:heap_live_slots) | |
| printf("\nMemo replacement (A -> B):\n") | |
| printf(" Net live objects after replace+GC: %+d\n", after2 - before2) | |
| # Bulk test: 1000 distinct blocks | |
| N = 1000 | |
| BLOCK_SRC = "->(s) { s.shout }" | |
| blocks = eval("[" + ([BLOCK_SRC] * N).join(",") + "]") | |
| GC.start; GC.start; GC.start | |
| before3 = GC.stat(:heap_live_slots) | |
| before3_mem = GC.stat(:malloc_increase_bytes) | |
| refined_all = blocks.map { |b| b.with_refinements(M) } | |
| GC.start; GC.start; GC.start | |
| after3 = GC.stat(:heap_live_slots) | |
| printf("\nBulk with_refinements x%d (same module, shared memo):\n", N) | |
| printf(" New live objects: +%d\n", after3 - before3) | |
| printf(" Per-block overhead: %.1f objects\n", (after3 - before3).to_f / N) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment