diff --git a/CLAUDE.md b/CLAUDE.md index 4c9d4a6e67..2181fdbb39 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation. -**Current Version:** 0.5.1372 +**Current Version:** 0.5.1373 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index 0a7dc3ce5c..e34b98f01f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5547,7 +5547,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "anyhow", "base64", @@ -5607,14 +5607,14 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "serde", ] [[package]] name = "perry-audio-miniaudio" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "cc", "libc", @@ -5622,7 +5622,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "anyhow", "inkwell", @@ -5639,7 +5639,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "anyhow", "perry-hir", @@ -5647,7 +5647,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "anyhow", "perry-hir", @@ -5655,7 +5655,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "anyhow", "perry-dispatch", @@ -5664,7 +5664,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "anyhow", "perry-hir", @@ -5672,7 +5672,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "anyhow", "base64", @@ -5684,7 +5684,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "anyhow", "perry-hir", @@ -5692,7 +5692,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "anyhow", "async-trait", @@ -5721,14 +5721,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "serde", "serde_json", @@ -5736,7 +5736,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1372" +version = "0.5.1373" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5747,7 +5747,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "anyhow", "clap", @@ -5762,7 +5762,7 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "block2", "objc2", @@ -5772,7 +5772,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "argon2", "perry-ffi", @@ -5780,7 +5780,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "perry-ffi", "reqwest", @@ -5789,7 +5789,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "bcrypt", "perry-ffi", @@ -5797,7 +5797,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "perry-ffi", "rusqlite", @@ -5805,7 +5805,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "perry-ffi", "scraper", @@ -5813,7 +5813,7 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "perry-ffi", "perry-runtime", @@ -5821,7 +5821,7 @@ dependencies = [ [[package]] name = "perry-ext-cron" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "chrono", "cron", @@ -5831,7 +5831,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "chrono", "perry-ffi", @@ -5839,7 +5839,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "perry-ffi", "rust_decimal", @@ -5847,7 +5847,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "perry-ffi", "serde_json", @@ -5855,7 +5855,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "perry-ffi", "rand 0.10.1", @@ -5863,7 +5863,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "perry-ffi", "perry-runtime", @@ -5871,14 +5871,14 @@ dependencies = [ [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "bytes", "http-body-util", @@ -5896,7 +5896,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "bytes", "lazy_static", @@ -5909,7 +5909,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "bytes", "h2", @@ -5933,7 +5933,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "lazy_static", "perry-ffi", @@ -5943,7 +5943,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "base64", "jsonwebtoken", @@ -5954,7 +5954,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "lru", "perry-ffi", @@ -5963,7 +5963,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "chrono", "perry-ffi", @@ -5971,7 +5971,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "bson", "futures-util", @@ -5983,7 +5983,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "chrono", "perry-ffi", @@ -5993,7 +5993,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "nanoid", "perry-ffi", @@ -6002,7 +6002,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "bytes", "perry-ffi", @@ -6015,7 +6015,7 @@ dependencies = [ [[package]] name = "perry-ext-node-forge" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "const-oid 0.9.6", "der 0.7.10", @@ -6034,7 +6034,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "lettre", "perry-ffi", @@ -6044,7 +6044,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "perry-ffi", "printpdf", @@ -6052,7 +6052,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "perry-ffi", "sqlx", @@ -6061,7 +6061,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "governor", "perry-ffi", @@ -6069,7 +6069,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "fast_image_resize", "image", @@ -6079,14 +6079,14 @@ dependencies = [ [[package]] name = "perry-ext-slugify" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-streams" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "lazy_static", "perry-ffi", @@ -6095,7 +6095,7 @@ dependencies = [ [[package]] name = "perry-ext-undici" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "perry-ffi", "perry-runtime", @@ -6104,7 +6104,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "perry-ffi", "uuid", @@ -6112,7 +6112,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "perry-ffi", "regex", @@ -6122,7 +6122,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "futures-util", "lazy_static", @@ -6135,7 +6135,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "brotli", "flate2", @@ -6145,7 +6145,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "dashmap", "once_cell", @@ -6154,7 +6154,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "anyhow", "perry-api-manifest", @@ -6172,7 +6172,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "anyhow", "perry-diagnostics", @@ -6184,7 +6184,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "anyhow", "base64", @@ -6226,14 +6226,14 @@ dependencies = [ [[package]] name = "perry-runtime-static" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "perry-runtime", ] [[package]] name = "perry-stdlib" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "aes 0.8.4", "aes 0.9.1", @@ -6328,14 +6328,14 @@ dependencies = [ [[package]] name = "perry-stdlib-static" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "perry-stdlib", ] [[package]] name = "perry-transform" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "anyhow", "perry-hir", @@ -6344,14 +6344,14 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "perry-ui-model", ] [[package]] name = "perry-ui-android" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "base64", "itoa", @@ -6368,7 +6368,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "rand 0.10.1", "serde", @@ -6378,7 +6378,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "base64", "cairo-rs 0.22.0", @@ -6401,7 +6401,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "base64", "block2", @@ -6417,7 +6417,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "base64", "block2", @@ -6432,7 +6432,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1372" +version = "0.5.1373" [[package]] name = "perry-ui-test" @@ -6443,11 +6443,11 @@ dependencies = [ [[package]] name = "perry-ui-testkit" -version = "0.5.1372" +version = "0.5.1373" [[package]] name = "perry-ui-tvos" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "base64", "block2", @@ -6463,7 +6463,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "base64", "block2", @@ -6479,7 +6479,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "block2", "libc", @@ -6492,7 +6492,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "base64", "libc", @@ -6509,14 +6509,14 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "perry-ui-windows", ] [[package]] name = "perry-updater" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "anyhow", "base64", @@ -6532,7 +6532,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1372" +version = "0.5.1373" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index c8023fb523..848cd6cdbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -315,7 +315,7 @@ codegen-units = 16 codegen-units = 16 [workspace.package] -version = "0.5.1372" +version = "0.5.1373" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry" diff --git a/changelog.d/7654-plan-close-7478-json-tape-scan.md b/changelog.d/7654-plan-close-7478-json-tape-scan.md new file mode 100644 index 0000000000..8847ed80cd --- /dev/null +++ b/changelog.d/7654-plan-close-7478-json-tape-scan.md @@ -0,0 +1,64 @@ +**docs(engine-plan): #7478 — the JSON tape's scan penalty is closed, re-measured at v0.5.1370 (PR #7654).** + +`docs/engine-plan.md`'s item 2 still described the JSON lazy tape as costing +**2.3× the direct parser on a full array scan**, with the v0.5.1299 sweep's +`field_access` inversion (optimized 2984 ms against idiomatic 1350 ms) as the +open problem. Every step of the issue's roadmap had shipped since — #7483 +(DirectParser float parity, the step-1 blocker), #7499 (reparse-on-materialize), +#7537 (early batch-flip trigger), #7539 (tape out of the old generation), plus +#7546 for the wrong-JSON defect the work surfaced — but nobody had re-run the +decomposition the issue was written around. + +Re-measured on the pinned quiet mini, 11 interleaved rounds, 176/176 samples, +every arm's checksum byte-identical to node 26.5.1: + +| phase | tape on: issue → now | tape off: issue → now | +|---|--:|--:| +| parse only | 210 → **160 ms** | 1220 → 1196 ms | +| parse + full scan | 3030 → **1233 ms** | 1287 → 1224 ms | +| parse + stringify | 254 → **171 ms** | 1756 → 1449 ms | +| field_access | 2981 → **1721 ms** | — → 1480 ms | + +The headline is gone: a full scan was 2.3× the direct parser and is now +**1.01×** (1233 vs 1224), while `roundtrip` — the path that must not pay for it +— improved 254 → 171 ms. + +**Why it closes rather than continues.** Measuring all four `PERRY_JSON_TAPE` × +`PERRY_GEN_GC` combinations instead of the two-switch `idiomatic` arm the +issue's floor came from, `field_access` decomposes additively: the tape costs +~200 ms whichever collector runs (241 under gen-GC, 195 under mark-sweep) and +the collector costs ~560 ms whether or not the tape exists (588 with, 542 +without). The issue had documented these as an *interaction* — scan σ 214.9 +under gen-GC against 8.8 under mark-sweep for the identical tape — and that +interaction is what #7539 removed. The ~200 ms is the tape *build* (parse-only +is 160 ms), structural and already named in #7537: the build is purely additive +whenever the whole tree ends up materialized anyway, and nothing can predict +scan-shaped access before the parse. The remaining ~560 ms is a +generational-collector term the tape-off arm carries identically, so it is +tracked with the GC work rather than as tape policy. + +**Method, recorded in the doc.** The "re-measure before scoping" warning goes +from three instances to four, and gains a second failure mode this ticket +demonstrates: **a stale floor is as misleading as a stale headline.** #7478's +acceptance bar was "materially under the 1350 ms `idiomatic` row", but +`idiomatic` is a measurement rather than a constant and had itself moved to +938 ms. Working the ticket as written would have chased a headline that four +merges had already fixed, and then declared failure against a bar that no +longer existed. When acceptance is "beat arm X", re-measure arm X in the same +run. + +**Verification.** Both reported runs are on a host gated to `rustc == 0` *and* +1-minute load < 1.5 held across two consecutive checks, and they agree +independently; a first attempt was discarded when another agent's build started +mid-run (load 1.86 → 8.57, every σ 50–600). The harness fails rather than +blanks on a cell that produced no sample or no checksum and asserts its +expected sample count, so a pass cannot be confused with "it never ran". +Subject liveness is asserted both ways: the tape is demonstrably engaged +(tape-on/tape-off differ 7.5× on parse-only and 8.5× on roundtrip), and +`cargo test -p perry-runtime --lib json_tape` passes 24/24 including the two +cases that distinguish *which producer ran* via `reparse_materializations()`. +Binaries built `-p perry -p perry-runtime-static -p perry-stdlib-static` with +`PERRY_RUNTIME_DIR` pinned, the archive mtime asserted to have moved, and +`PERRY_NO_AUTO_OPTIMIZE=1` on every compile. + +Documentation only — no runtime or codegen change. diff --git a/docs/engine-plan.md b/docs/engine-plan.md index bded91b1bc..5956214624 100644 --- a/docs/engine-plan.md +++ b/docs/engine-plan.md @@ -308,48 +308,72 @@ each moved by an order of magnitude instead of a few percent: delete-safe index walk over the flat entries it is **5 ms**. Perry already won lookup before the fix (76 ms vs node 115, bun 103). -**Read this section before picking up any row above.** Three times this campaign +**Read this section before picking up any row above.** Four times this campaign a ticket was worked from a headline number that had already collapsed — #7510 -(33.6% → 11%), `layout_forget_object` (14.5% → 3.0% → 1.7%), and `layout_note_slot` -(7.5% → 0.03%, correctly closed with **no code at all**). Re-measure the row -before profiling it. +(33.6% → 11%), `layout_forget_object` (14.5% → 3.0% → 1.7%), `layout_note_slot` +(7.5% → 0.03%, correctly closed with **no code at all**), and #7478 (a 2.3x scan +penalty that was 1.01x by the time anyone re-ran it). Re-measure the row before +profiling it. -### JSON polyglot legs — the tape is a net negative on scans +#7478 adds a second failure mode to watch for: **a stale floor is as +misleading as a stale headline.** Its acceptance bar was "materially under the +1350 ms `idiomatic` row" — but `idiomatic` is a *measurement*, not a constant, +and it had moved to 938 ms. A bar quoted as a number silently becomes a bar +quoted against a different build. When acceptance is "beat arm X", re-measure +arm X in the same run, never carry its number forward. -Same run. `roundtrip` is the crown jewel and `field_access` is the problem: +### JSON polyglot legs — the tape's scan penalty is closed (#7478) + +`roundtrip` is the crown jewel and `field_access` was the standing problem: at +the v0.5.1299 sweep the optimized configuration was 2.2x SLOWER than the +unoptimized one at 3.6x the RSS, with a sigma of 136 against every other row's +under 5. | leg | perry optimized | perry idiomatic | bun | node | rust serde_json | |---|--:|--:|--:|--:|--:| | roundtrip | **192 ms** (82 MB) | 1307 ms | 216 | 379 | 178 | | field_access | **2984 ms** (219 MB, sigma 136) | **1350 ms** (61 MB) | 218 | 380 | 183 | -Perry **wins roundtrip** against both JS runtimes and lands within ~8% of Rust -serde_json. `field_access` was the standing problem: the optimized configuration -was 2.2x SLOWER than the unoptimized one at 3.6x the RSS, with a sigma of 136 -against every other row's under 5. - -**That inversion is now closed** (#7478 → #7537 early batch flip, then #7539 -tape side-allocation), measured on the same host: +**That inversion is closed** — #7483 (DirectParser float parity), #7499 +(reparse-on-materialize), #7537 (early batch flip), #7539 (tape +side-allocation), with #7546 fixing the wrong-JSON defect the work surfaced. +Re-measured at **v0.5.1370** on the pinned quiet mini, 11 interleaved rounds, +every arm's checksum identical to node 26.5.1: -| `field_access` | median | sigma | peak RSS | +| phase | tape on, then → now | tape off, then → now | +|---|--:|--:| +| parse only | 210 → **160 ms** | 1220 → 1196 ms | +| parse + full scan | 3030 → **1233 ms** | 1287 → 1224 ms | +| parse + stringify | 254 → **171 ms** | 1756 → 1449 ms | +| field_access | 2981 → **1721 ms** | — → 1480 ms | + +The issue's headline was that a full scan cost **2.3x the direct parser** +(3030 vs 1287). It is now **1.01x** (1233 vs 1224): the tape neither wins nor +loses a scan, which is the correct resting state for a structure whose whole +value is on the paths that skip materialization. `roundtrip` — the memcpy path +this must not regress — went the other way, 254 → 171 ms. + +**The two remaining terms are now orthogonal, which is the real result.** The +issue documented an *interaction* (scan sigma 214.9 under gen-GC vs 8.8 under +mark-sweep for the identical tape). Measuring all four `PERRY_JSON_TAPE` x +`PERRY_GEN_GC` combinations, `field_access` decomposes additively: + +| | tape on | tape off | tape term | |---|--:|--:|--:| -| sweep (v0.5.1299) | 2984 ms | 136 | 219 MB | -| after #7537 | 2043 ms | 146 | 195 MB | -| **after #7539** | **1809 ms** | **17.3** | **155 MB** | - -Sigma collapses **8.3x** — that was the headline symptom, not the median — and -the decisive result is that **turning the tape ON is no longer worse than -turning it OFF**. The tape-off arm still carries sigma 117.2 and 168 MB, so the -residual variance and footprint belong to the generational collector's behaviour -on this workload and have nothing left to do with the tape. The GC trace agrees -to the cycle: 19 cycles / 9 full / 6 `old_gen_bytes` becomes **14 / 5 / 2**, -which is the `PERRY_JSON_TAPE=0` arm's profile exactly. +| gen-GC | 1721 ms | 1480 ms | **+241** | +| mark-sweep | 1133 ms | 938 ms | **+195** | +| gen-GC term | **+588** | **+542** | | -`roundtrip` — the memcpy path this must not regress — **improved**, 201 → 193 ms, -with peak old-generation in-use down 39.6 → 14.1 MB. +The tape costs ~200 ms whichever collector runs, and the collector costs +~560 ms whether or not the tape exists. The ~200 ms *is* the tape build (parse +only is 160 ms) and is structural, exactly as #7537 recorded: the build is +purely additive whenever the whole tree ends up materialized anyway, and +nothing can predict scan-shaped access before the parse. -Still open: 1809 ms has not reached the 1350 ms idiomatic floor, and the gap is -now collector behaviour rather than tape design. +So `field_access`'s remaining ~560 ms is a **generational-collector** term that +the tape-off arm carries identically. It is not a tape-policy problem and does +not belong to #7478; it is the same collector behaviour the GC campaign is +already working, on a workload that happens to reach it through `JSON.parse`. ### Gates and blockers @@ -379,8 +403,23 @@ now collector behaviour rather than tape design. rooting re-reads are material on a spread-heavy workload. If they are, the answer is hoisting them (#7487's pooled-alloca precedent), never removing them — they close real use-after-frees. -2. **#7478 — the JSON tape's scan path**, where our optimized build is 2.2x - slower than our unoptimized one. The 1350 ms idiomatic row is the floor. +2. ~~**#7478 — the JSON tape's scan path**~~ — **closed, re-measured at + v0.5.1370.** The headline (a full scan costs 2.3x the direct parser) is + gone: 3030 → 1233 ms against the tape-off arm's 1224, i.e. **1.01x**, and + `roundtrip` improved 254 → 171 ms rather than paying for it. The four-way + `PERRY_JSON_TAPE` x `PERRY_GEN_GC` decomposition above is what closes it — + the tape term (~200 ms, its own build) and the collector term (~560 ms) are + now **additive and independent**, where the issue had documented them as an + interaction. The residual on this benchmark is therefore the collector's, + carried identically by the tape-off arm, and is tracked with the GC work + rather than here. + + Worth recording as method: **this ticket's headline was stale in both + directions.** The 2.3x had been fixed by four merges nobody had re-measured + together, and its stated floor (the 1350 ms `idiomatic` row) had itself + moved to 938 ms — so coding to the ticket would have chased a target that + had already moved and declared failure against a bar that no longer existed. + Two switches, measured one at a time, was the whole difference. 3. ~~**`_tlv_get_addr` — thread-local addressing**~~ — **measured out (#7565).** Re-measuring first is what decided the design: the 27.0% was real, but the ticket's "41 distinct call-graph sites, this is diffuse" was not — **seven