Summary
Community-reported (Discord): an adaptive counting benchmark runs ~25× slower than Node. Reproduced and decomposed. The dominant cost is plain number[] element access (counts[v] read + write in a hot loop); function-call overhead is secondary; arithmetic itself is near-native.
Reproduction (deterministic variant, 5M iterations, byte-identical output vs Node)
Inner loop: const v = random(); counts[v] = (counts[v] || 0) + 1;
Ablation (per-item ns, perry 0.5.1220 vs Node 26.3, same machine/load, ratios stable):
| variant |
Node |
Perry |
ratio |
| pure inline math (no call, no array) |
1.6 |
4.6 |
~3× |
+ random() as function call |
10.5 |
35.4 |
3.4× |
+ Int32Array counts |
10.7 |
69.4 |
6.5× |
+ plain number[] counts (packed, pre-filled) |
9.4 |
244.7 |
26× |
original shape (holey []) |
9.2 |
263.1 |
29× |
So of the ~214 ns/item on the original script: ~210 ns is the plain-array element read+write, ~31 ns the call, math is fine.
Analysis
Repro files
Deterministic benchmark + ablation harness available on request (seeded LCG replacing Math.random, fixed batch sizes; outputs byte-identical to Node).
Summary
Community-reported (Discord): an adaptive counting benchmark runs ~25× slower than Node. Reproduced and decomposed. The dominant cost is plain
number[]element access (counts[v]read + write in a hot loop); function-call overhead is secondary; arithmetic itself is near-native.Reproduction (deterministic variant, 5M iterations, byte-identical output vs Node)
Inner loop:
const v = random(); counts[v] = (counts[v] || 0) + 1;Ablation (per-item ns, perry 0.5.1220 vs Node 26.3, same machine/load, ratios stable):
random()as function callInt32Arraycountsnumber[]counts (packed, pre-filled)[])So of the ~214 ns/item on the original script: ~210 ns is the plain-array element read+write, ~31 ns the call, math is fine.
Analysis
number[]is a boxed-element heap array — every element read unboxes and every write reboxes + barriers, and V8-style packed-smi element storage does not exist.docs/representation-selection-rfc.md. The scalar/call layers are covered by shipped/in-flight phases (perf(codegen): representation-selection Phase 1 — canonical unboxed i32 locals #6903 canonical i32 locals; the Phase 2 specialized ABI covers the call overhead; the Int32Array row benefits from perf(codegen): inline checked-f64 typed-array-param reads in numeric context #6883/perf(codegen): native-i32 residency for int-typed-array-seeded locals (bcrypt _encipher) #6898). The plain-array element representation is Phase 4 (typed heap) territory: unboxed element storage for arrays proven numeric-only.number[]whose writes are all proven numeric (this benchmark'scountsqualifies:new Array(100).fill(0)or[], indexed only by a 0..99 int, values onlycount + 1). The existing packed-f64 loop-fact machinery covers counter-indexed shapes but not this value-indexed histogram shape.Repro files
Deterministic benchmark + ablation harness available on request (seeded LCG replacing
Math.random, fixed batch sizes; outputs byte-identical to Node).