perf(vm,rt): add PreparedCall for per-element callback loops; adopt in some - #726
Merged
Conversation
This was referenced Aug 12, 2026
nnunley
approved these changes
Aug 12, 2026
mparrett
force-pushed
the
perf/vm-prepared-call
branch
from
August 12, 2026 16:28
ec1a462 to
4d5995f
Compare
An error occurred while trying to automatically change base from
perf/vm-defer-free-dispatch
to
main
August 12, 2026 19:03
…n some A native seq fn calling a bytecode predicate pays resolution (resolveBytecodeCall), frame-pool mutex traffic, and full frame init on every element, although all of it is per-call-site constant. PreparedCall resolves the callable once, owns one frame, and per call only resets args/ip/sp plus the fields a callee can rebind: a tail call in the body retargets the frame's code/consts/closedOvers via installBytecodeCall, and an error unwind can leave stale handlers, so the reset covers those too (pinned by tests). Non-bytecode and variadic targets return nil and callers keep the generic ec.Invoke path. Adopt it in rt.Some. On the #700 some-over-range workload (1e6 elements, interleaved medians, darwin/arm64) this takes the callback-heavy shape from ~1.5x of v1.12.2 to below the v1.12.2 baseline while keeping #644's stack-safety. reduce's closure path, every?, and the lazy-seq step fns are the follow-up adopters. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
native_prims.go is a manifest input; the some-adopter edit left the committed digest stale, failing go test ./pkg/genmanifest on this head. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Call1 is the only entry point, but PrepareCall accepted any arity: arity 0 panicked at args[0], arity 2+ left Go nil interfaces in the unpopulated slots and passed them into bytecode. Prepare only arity 1 so other arities fall back to the generic Invoke path; widen as CallN methods land. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mparrett
force-pushed
the
perf/vm-prepared-call
branch
from
August 12, 2026 19:41
4d5995f to
ae28e9a
Compare
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.
First implementation PR from #720, stacked on #719. A native seq fn calling a bytecode predicate pays three costs on every
ec.Invokethat are constants of the call site, not of the call:resolveBytecodeCall(same target every element), twoframePoolMulock/unlock pairs, and full frame init/teardown.PreparedCallresolves the callable once, owns one frame, and per call resets only what a callee can have changed.The reset covers more than
args/ip/sp, and the tests pin why: a tail call in the callee body rebinds the owned frame'scode/consts/closedOversthroughinstallBytecodeCall, an error unwind can leave stalehandlers, and tracing can armdebugmid-walk.PrepareCallreturns nil for variadic, native, and other non-bytecode targets, and for arities without a matchingCallNentry point (Call1is the only one here), sort.Somekeeps its reused-fargsec.Invokefallback and the change stays incremental. APreparedCallis single-owner per native call activation; nested uses each prepare their own.Adopted in
rt.Some(both the chunked and linear walk arms). Measured on(some (fn [x] (< 9999999 x)) (range 1000000)), darwin/arm64, interleaved mins: 50.1 ms vs 70.9 on the base branch and ~58.4 on v1.12.2 — below the release baseline, with #644's stack-safety kept. xsofy's headless engine bench shows the same direction per game turn (table on #719).A note on the
perf-repeatlabel: the pr-fast micro families don't route throughrt.Some, so the lane serves as a regression guard on the vm families here rather than the measurement of the win — the e2e numbers above are the claim.New tests:
prepared_call_test.go(repeat invocation, closure captures, the tail-call rebind reset, reuse after error, native/variadic and unsupported-arity rejection) andsome_prepared_test.go(bytecode pred over chunked + linear seqs, native fallback). Suites green:pkg/vm,pkg/rt,test/; linux, js/wasm, plan9 builds.