What happened
Between 0.5.1260 and 0.5.1265, JSON.parse of a small nested object in a hot
loop developed a throughput cliff: ~1 µs/parse up to ~10k parses, then a step
change to ~250–375 µs/parse.
Measurements (min ms, from-source builds, same host)
Fixture: {"id":42,"name":"fixture","active":true,"tags":["alpha","beta","gamma",7],"meta":{"level":3,"score":512,"child":{"k":9,"items":[1,2,3,4,5]}}}
| parses (N) |
0.5.1260 |
0.5.1265 |
| 10,000 |
21 |
12 (faster) |
| 20,000 |
42 |
5842 |
| 40,000 |
83 |
15023 |
Control at N=20,000: manual build of the identical graph (no parse) = 12 ms on
1265; parsing any simpler shape ({"a":1}, one array, one string, 3-deep
nesting) = 2–7 ms. The trigger needs the full multi-container graph and high
volume — i.e. how JSON.parse allocates/tenures, not the allocation count.
Minimal reproduction
var SRC = '{"id":42,"name":"fixture","active":true,"tags":["alpha","beta","gamma",7],"meta":{"level":3,"score":512,"child":{"k":9,"items":[1,2,3,4,5]}}}';
function work() {
var chk = 0;
for (var i = 0; i < 20000; i++) { var o = JSON.parse(SRC); chk = (chk + o.id + o.meta.score) | 0; }
return chk | 0;
}
work();
var best = 1e9, c;
for (var r = 0; r < 3; r++) { var t = Date.now(); c = work(); var d = Date.now() - t; if (d < best) best = d; }
console.log(best + " ms"); // ~43 ms on 0.5.1260, ~5800 ms on 0.5.1265
perry compile m.js -o m && ./m. Bisecting across the N=10k→20k knee should
localize it (window has several perf(gc)/nursery changes: #7076/#7079/#7088/#7051/#7057, open #7056).
Environment
Perry 0.5.1260 (2e105ca) vs 0.5.1265 (a3b31c0d), cargo build --release -p perry
from tagged source, each linked to its own full runtime. macOS 27.0 arm64.
What happened
Between 0.5.1260 and 0.5.1265,
JSON.parseof a small nested object in a hotloop developed a throughput cliff: ~1 µs/parse up to ~10k parses, then a step
change to ~250–375 µs/parse.
Measurements (min ms, from-source builds, same host)
Fixture:
{"id":42,"name":"fixture","active":true,"tags":["alpha","beta","gamma",7],"meta":{"level":3,"score":512,"child":{"k":9,"items":[1,2,3,4,5]}}}Control at N=20,000: manual build of the identical graph (no parse) = 12 ms on
1265; parsing any simpler shape (
{"a":1}, one array, one string, 3-deepnesting) = 2–7 ms. The trigger needs the full multi-container graph and high
volume — i.e. how
JSON.parseallocates/tenures, not the allocation count.Minimal reproduction
perry compile m.js -o m && ./m. Bisecting across the N=10k→20k knee shouldlocalize it (window has several
perf(gc)/nursery changes: #7076/#7079/#7088/#7051/#7057, open #7056).Environment
Perry 0.5.1260 (
2e105ca) vs 0.5.1265 (a3b31c0d),cargo build --release -p perryfrom tagged source, each linked to its own full runtime. macOS 27.0 arm64.