probe(vm): revert #645 to measure the explicit frame chain's cost - #691
probe(vm): revert #645 to measure the explicit frame chain's cost#691mparrett wants to merge 1 commit into
Conversation
MEASUREMENT PROBE — do not merge. This exists so the perf-repeat lane can A/B main against main-minus-#645 on one runner. `some` over (range 1000000) is 1.38-1.49x slower on main than before #645 (60.5 -> 83.7 ms local, holding at 10x scale) with allocation flat, so the cost is CPU in the call path, not garbage. It is clean at #639, which rules out the primitive hoist. The tip CPU profile puts (*Frame).runLoop, runtime.(*_panic). nextDefer and runtime.tryDeferToSpanScan in the hot path, which points at this commit. The same per-invoke cost plausibly explains why `get`, `nth`, `str`, `conj` and `deref` all sit 1.06-1.18x above pre-#639 with no fast-path story, and the residual left after #686. Head faster than base by X% here means #645 costs X%. FuncInvoke, FrameDispatch and FrameAlloc are the families to read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Numbers are in, so this probe has done its job and I am closing it. Result
The capability staysAt 1M levels of non-tail recursion Follow-up analysis — which defers were priced and ruled out, where the remaining cost sits, and why the |
Measurement probe. Do not merge. This reverts #645 so the
perf-repeatlane can A/Bmainagainstmain-minus-#645 on a single runner. It will be closed once the numbers are in.What it measures
someover(range 1000000)is 1.38–1.49x slower onmainthan before #645 — 60.5 → 83.7 ms locally, holding at 10x the element count — with allocation flat at 29 MB vs 33 MB, so the cost is CPU in the call path rather than garbage. It is clean at #639 (1.03x), which rules out the primitive hoist.some's two implementations are byte-identical in logic, so nothing in the body accounts for it.The
mainCPU profile puts(*Frame).runLoop,runtime.(*_panic).nextDeferandruntime.tryDeferToSpanScanin the hot path, which is what points at this commit.Read
FuncInvoke,FrameDispatchandFrameAlloc. Head faster than base by X% means #645 costs X%.Why it is worth a lane
If it lands, one commit explains a spread of unattributed slowdowns. On
mainagainst pre-#639,getis 1.18x,nth1.17x,str1.11x,conjandderef1.06x, none of which has a fast-path story of its own, and #686 leaves a 1.20x residual onreduceafter restoring the fast paths it lost. A per-invoke cost in the VM would cover all of it.Local A/B is 3 warmup / 12 runs on darwin/arm64, hyperfine, both orders:
someover(range 1000000)c7d3da31(pre-#645)7c5a992a(#645)mainFiled against
mainrather than measured locally because the laptop that produced those numbers reached load 15 mid-session and inflated a 54 ms probe to 187 ms. The interleaved same-runner lane is the instrument that does not have that failure mode.