Repository navigation
Opt target 2 — iso_miss (2.11x) and interp (1.48x): tree-walking interpreters #8591
Description
Opt target 2 — iso_miss (2.11x) and interp (1.48x): tree-walking interpreters
Treat these as one task. They share their shape, and a win on one plausibly moves the
other — check whether it does and report both either way.
Measurement — quiet M1 mini, 5 shuffled interleaved repeats, verdict CLEAN, perry fefdc367b
| node | perry | ratio | |
|---|---|---|---|
| iso_miss wall | 0.338 s | 0.714 s | 2.11x |
| — instructions | 6.264 G | 12.462 G | 1.99x |
| — cycles | 1.122 G | 2.281 G | 2.03x |
| — IPC | 5.58 | 5.46 | — |
| — peak RSS | 89.4 MB | 30.9 MB | 0.35x |
| interp wall | 0.324 s | 0.481 s | 1.48x |
| — instructions | 5.973 G | 8.643 G | 1.45x |
| — cycles | 1.079 G | 1.530 G | 1.42x |
| — IPC | 5.54 | 5.65 | — |
| — peak RSS | 94.7 MB | 31.3 MB | 0.33x |
Both are genuinely CPU-bound and the diagnosis is unambiguous: instruction ratio, cycle
ratio and wall ratio all agree to within a few percent, and IPC is essentially identical to
Node's. Perry is executing the same quality of code, just ~2x and ~1.45x more of it. Fewer
instructions is the only lever; there is no scheduling or stall story to find.
Workloads: gc-handoff/apps/iso_miss.ts and apps/interp.ts — tree-walking interpreters for
small functional languages. Recursive union allocation, closures capturing environments,
polymorphic dispatch, string building, deep recursion, string-keyed Maps.
History worth knowing
iso_missregressed +24.8% at perf(codegen): retain string accumulators in concat chains #8417 (a codegen string-accumulator change). perf(string): bypass empty accumulator copies #8487
recovered most of it (14.788 G → 12.470 G instructions, −15.7%), and perf(runtime): resolve #8434 string-builder round trips — reconciling #8568 and #8571 #8581's join/repeat work
landed since. Roughly +5% of that original regression was never accounted for.interpimproved −4.61% instructions at perf(gc): skip tiny per-object layout masks #8539 (per-object layout-mask cutoff 2 → 4). That
came from profiling, which foundevalNodeat 61.1% of self samples across 30 runs — not
the dynamic-numeric-envelope problem the brief had guessed. Profile before assuming.- An unattributed step in the 08-21 window moved
iso_miss+7.9%; it has since been flat, so it
was a one-time change rather than a trend. Attribution is still open if you want it.
Method
Profile first, with PERRY_KEEP_SYMBOLS=1 PERRY_DEBUG_SYMBOLS=1, aggregated over many runs —
these programs are 300–700 ms and a single sample yields almost nothing.
Because IPC already matches Node's, the question is what work exists that Node does not do:
redundant guards, allocation Node avoids, dispatch Node caches, or a per-node cost in the
interpreter loop that should be hoisted.
Acceptance
- Instruction reduction on either row, with the effect on both reported.
- All 20 corpus programs byte-exact; whole-corpus timing before and after, every row moving
more than 1% reported. - RSS is already 0.33–0.35x Node. Do not trade it for speed; quantify any cost.
- Instructions retired is the primary signal — the bench host is contended and wall ranges
overlap.
Activity
proggeramlug commentedon Aug 22, 2026
Refreshed against the 2026-08-22 sweep at
adbebbe9d(verdict CLEAN, quiet mini, 5 shuffled interleaved repeats). Both rows improved but the diagnosis is unchanged, so this stays open.iso_missinterpThe instruction ratio still tracks the wall ratio almost exactly on both — 2.07 vs 1.96, and 1.42 vs 1.41. These remain genuinely CPU-bound with IPC comparable to Node's, so fewer instructions is still the only lever and there is no stall or scheduling story to find.
#8597 landed since (inline equality for short heap strings, targeting exactly the identifier comparisons these interpreters do) and is part of the −2% / −4% above. Worth knowing it is already applied, so it is not available as a fresh idea.
For contrast, both of the other rows from the original ticket set have now changed character:
typed_arrayfell 6.29x → 3.87x with instructions 8.52x → 2.24x, so its wall ratio now exceeds its instruction ratio and it needs a stall-oriented look rather than more work-deletion.pipelineremains the one row where instructions (1.74x) far exceed cycles — scheduling-bound, not CPU-bound. This ticket is the only one of the three still cleanly in the "delete executed work" category.perf(codegen): delete six instructions from the generic property-read…