Skip to content

Check inline whether a VM's stack may shrink, so a VM call skips shrink when it cannot - #48

Merged
folbricht merged 1 commit into
mainfrom
shrink-fast-path
Oct 20, 2026
Merged

folbricht merged 1 commit into
mainfrom
shrink-fast-path

Conversation

@folbricht

Copy link
Copy Markdown
Owner

The new benchmark run in #47 showed a call into a private VM about 5 ns slower than in v0.4.0. Counting instructions per call (the slope between 10M and 30M calls under perf stat) traced it to 5a734e0 in #40, which went from 781 to 828 instructions per call. That commit made VM.leave call shrink after every call. Two costs came with it:

  • shrink ran its full check even when there was nothing to shrink: a stack frame, two max computations, and the stack and frame comparisons, about 35 instructions.
  • VM.leave no longer fit the inlining budget, so every call also paid for a call into leave.

shrink is now a small guard that the compiler inlines, with cost 77 against a budget of 80. It runs the full check, now shrinkCheck, only when the stack or the frames are past the pool's bounds, or a shrink is already counting down (quietChecks != 0). In every other case the full check could only set quietChecks to 0, which it already is, so nothing changes in behaviour. Renew and collectgarbage use the same guard.

BenchmarkAPICallPrivate instructions per call ns per call, 8 interleaved runs
before #40 781 89.4
main 828 97.4
this PR 803 93.3

Calls through a shared Script don't go through VM.leave and are unchanged (924 instructions).

The remaining 22 instructions are the call into VM.leave and the guard itself. Getting them back would mean moving the check into reset, which every other path would then pay for, so I didn't.

TestDeepStackShrinks covers the guard: it fails if the guard never lets the full check run.

…nk when it cannot

Since the stack shrink was added, every VM call ran shrink's full check, about 35 instructions, and VM.leave was no longer inlined: 47 more instructions per call into a private VM. shrink is now an inlined guard that runs the full check only when the stack or frames are past the pool's bounds or a shrink is counting down; otherwise the check could only reset a counter that is already zero. A call into a private VM goes from 828 to 803 instructions.
@folbricht
folbricht merged commit a5c537c into main Oct 20, 2026
17 checks passed
@folbricht
folbricht deleted the shrink-fast-path branch October 20, 2026 11:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant