Repository navigation
perf(vm): int64 fast path + batched arg-boxing for sprintf/String#% - #114
Merged
Merged
Conversation
The format binding routed every operand through the go-ruby-format
Value interface, and every integer through math/big: a profile of a
sprintf-heavy loop showed the format path was GC-bound on those
allocations (not on re-parsing the format string).
Two allocation cuts on the Kernel#sprintf / Kernel#format / String#%
path:
* formatValue implements the library's Int64Fast fast-path, so a
native Integer (and an int64-range Bignum) formats without
allocating a *big.Int. This activates once go.mod consumes the
go-ruby-format/format int64 fast path; it is inert but harmless
against the current release.
* formatString wraps the N operands in a single backing array
referenced by pointer, replacing one interface box per argument
with one allocation for the whole batch.
Isolated format path (rbgo objects, bypassing the interpreter):
before 688 ns/op, 32 allocs/op
after 418 ns/op, 11 allocs/op (1.65x, once the library bump lands)
Byte-exact MRI output is unchanged (the differential/golden sprintf
tests stay green); internal/vm keeps 100% coverage, with a new
Int64Fast branch test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Activates the two library-side perf fixes end-to-end in rbgo: - go-ruby-format: math/big-free int64 fast path (format engine 1.65x; headline sprintf gap is VM-general per-op alloc, not the formatter). - go-ruby-zlib: pooled flate engines via Reset (zlib roundtrip 51554->6653 ns/op = 0.88x MRI, now faster than C zlib; byte-identical output, MRI interop preserved). Adds klauspost/compress (indirect). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This branch predated #113 (the go-ruby-prime segmented-sieve bump), so its go.mod still pinned the pre-fix version. Match main's pin so merging this PR does not regress Prime.each back to ~12.8x MRI. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A CPU + heap profile of a sprintf-heavy loop showed the format path is GC-bound on allocations, not on re-parsing the format string (parsing is <1% of CPU). The two sources: every operand boxed into the go-ruby-format
Valueinterface one alloc at a time, and every integer routed throughmath/bigeven when it fit in anint64.This PR cuts both on the
Kernel#sprintf/Kernel#format/String#%/IO#printfpath:formatValue.Int64Fast— implements the library's optional int64 fast-path, so a nativeInteger(and an int64-rangeBignum) formats without allocating a*big.Int. Inert-but-harmless against the current library release; it activates oncego.modconsumes the companion library change.formatStringwraps the N operands in a single backing array referenced by pointer, replacing one interface box per argument with one allocation for the whole batch.Companion library change + required go.mod bump
The int64 fast path lives in go-ruby-format/format#1. After it merges, bump this module to consume it:
Until then this PR gives the batched-arg-boxing win alone;
Int64Fastis dormant (the current library has no fast-path interface to call it).Results
Isolated format path (rbgo objects, bypassing the interpreter):
1.65x, once the library bump lands. Note the end-to-end 5-way harness op is dominated by VM interpreter work (checksum
reduce, string interpolation) around the sprintf call, so the engine speedup moves the isolated sprintf-only Ruby op ~15% and the full harness op ~1%; the format engine itself is 1.65x.Correctness
internal/vmkeeps 100% coverage (newInt64Fastbranch test);go test -race ./internal/vmgreen underTZ=UTC; cross-builds green on all 6 64-bit arches + windows.🤖 Generated with Claude Code