Repository navigation
perf: bind every direct call through one path - #86
Merged
Merged
Conversation
Record where WeavePy stands against CPython 3.14 (warm time 1.28x, peak memory 1.71x, startup 0.74x), the callgrind breakdown of the remaining gap, and the ranked plan for the structural changes.
A call between two compiled functions bound a pooled InlineAct, pushed it on the core loop's switch, and entered its caller on the lazy spine's pending list, about 480 instructions in the call helper alone. It now binds a per-depth LightAct record instead: the callee's frame and native state, with its code, globals and builtins borrowed from the callable and no switch or pending-list work at all. Nothing outside the call sees a light activation. Whatever needs the core loop's view of it first reifies it, and the light activations that called it, into the ordinary direct activations the old path bound: the callee leaving its native code, any helper that switches the running activation or publishes its caller (ensure_heavy), and a return that isn't clean. A site whose light calls keep being reified binds ordinary activations instead. Positional and keyword calls, operator dunders, property getters and callees with cells all take the light path; core_bind_kw_direct is gone. go retires 6% fewer instructions, tomllib_loads 3%, deepcopy 1%; the suite's geomean 0.7%. New burst-stat lines count light calls and reifications, and a regression test covers frames, tracebacks, locals, finalizer timing, the recursion limit and warning stack levels from hot direct calls.
Replace the light activations with one bind for every direct call between compiled functions (frame_jit::direct_enter): positional and keyword arguments, defaults, and fresh or closure cells, into a pooled or site-warm InlineAct pushed on the core loop's switch, as the core loop binds its own calls. The light records skipped the switch and pending-list registration, about 40 instructions a call, but anything that switched activations had to reify them first, which kept constructors, generator resumes and builtin lanes off them and took 15 call sites and a frame swap to get right. They also borrowed the callee's code, which a reassigned __code__ could free mid-call. With one representation, every shape runs direct and nothing needs reifying: core_bind_kw_direct, bind_call_cells and core_bind_plain's DIRECT variant are gone. Instructions are within 0.3% of before on the call-heavy workloads (the geomean of go, raytrace, deepcopy, tomllib_loads, mini_interpreter, sudoku, chaos and exceptions is 0.997). The next step emits the call in line from compiled code against this record.
Record the design the call-path measurements point to: activations as records on a per-thread stack, linked to their callers, that the interpreter, the frame JIT and generators all run, replacing the pooled InlineAct, the lazy pending list, the per-call native State and the frameless leaf paths. The RFC gives the measured per-call cost breakdown, a five-stage plan, and the expected effect.
This was referenced Oct 21, 2026
…ipt work in the progress note
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.
Item 1 of the plan in
docs/PERFORMANCE-PROGRESS.md(added here): the call paths.A direct call between two compiled functions bound its activation through several paths (
core_bind_plain::<true>for positional calls,core_bind_kw_directfor keyword calls,bind_call_cellsfor callees with cells), with per-site warm slots. They now share one bind,frame_jit::direct_enter, into an ordinaryInlineAct; the variants are gone.The branch first tried light direct activations, which skipped the core loop's switch and the pending list (
goretired 6% fewer instructions). They were dropped in the second commit: anything that switches activations had to reify them first, which kept constructors, generator resumes and builtin lanes off them, took a frame swap and 15 reify points, and borrowed code that a reassigned__code__could free mid-call.Instructions on the call-heavy workloads are within 0.3% of
main(geomean 0.997 overgo,raytrace,deepcopy,tomllib_loads,mini_interpreter,sudoku,chaos,exceptions): this PR is a simplification, not a speedup. The progress note records why the remaining call cost (about 480 instructions a direct call) is spread over the activation protocol rather than any one step.Validation
test_light_direct_calls.py, which covers frames, tracebacks, locals, finalizer timing, the recursion limit and warning stack levels from hot direct calls).RUST_MIN_STACK=8388608, all features): 430/430.