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
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.
Opt target 2 —
iso_miss(2.11x) andinterp(1.48x): tree-walking interpretersTreat 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
fefdc367bBoth 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.tsandapps/interp.ts— tree-walking interpreters forsmall 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 #8487recovered 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). Thatcame from profiling, which found
evalNodeat 61.1% of self samples across 30 runs — notthe dynamic-numeric-envelope problem the brief had guessed. Profile before assuming.
iso_miss+7.9%; it has since been flat, so itwas 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
sampleyields 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
more than 1% reported.
overlap.