bench: JSPI vs asyncify A/B — harness + results
Adds pcbnew-large-perf.spec.ts (PERF_LARGE-gated: repeated cold loads, vme-wren/jetson opens, rAF + distinct-glcanvas-frame FPS under throttle, wasm/JS heap checkpoints), fetchIntoMemfs + openAndWait/sampleMemory/ measureFpsDetailed perf-utils, dual 9.99+10.0 config seeding in pcbnew.html so foreign-branch builds boot wizard-free, and the full benchmark report + raw data under docs/features/async/migration-evidence/. Headlines: wasm 94 vs 113 MB raw (18.6 vs 36.7 MB gzip), post-link tail 1.6 s/49 MB vs 63 s/6.1 GB per build, cold load −40 %, 27.7 MB board open −45 %, real redraws +68 % at 4× throttle, boot heap −31 %. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
This commit is contained in:
parent
9c475a804e
commit
da299ed6f9
12 changed files with 707 additions and 11 deletions
|
|
@ -216,6 +216,10 @@ same-origin serving works. Not fixed in the cleanup — tracked here.
|
|||
|
||||
## 10. Migration evidence
|
||||
|
||||
- **A/B benchmark vs the asyncify build** (2026-08-14): build time / build
|
||||
memory / bundle size / load / open / FPS / heap —
|
||||
[`migration-evidence/jspi-vs-asyncify-bench-2026-08.md`](migration-evidence/jspi-vs-asyncify-bench-2026-08.md)
|
||||
(raw data in `migration-evidence/bench-data-2026-08/`).
|
||||
- **Workflow results**: `migration-evidence/wf-result-11.json` /
|
||||
`wf-result-12.json` (the durable spike output; the rest of the
|
||||
`.jspi-assets/` spike tree was scratch and is gone — its ignore rule came
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
A-S1a,175,0,19584995328
|
||||
A-S1b,427,0,22525415424
|
||||
A-S2a,165,0,21735530496
|
||||
A-S2b,248,0,23429591040
|
||||
A-compileonly,91,0,23662247936
|
||||
A-S3,819,0,23760617472
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
B-S1a,379,0,20131172352
|
||||
B-S1b,154,0,20515237888
|
||||
B-S2a,122,0,20695916544
|
||||
B-S2b,95,0,20612022272
|
||||
B-S3,26,2,4479885312
|
||||
B-S3,305,0,20694478848
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"jspi": {
|
||||
"kicad_editor.wasm": {
|
||||
"raw": 98664038,
|
||||
"gzip": 19469712,
|
||||
"brotli": 14173019
|
||||
},
|
||||
"kicad_editor.js": {
|
||||
"raw": 474692,
|
||||
"gzip": 116955,
|
||||
"brotli": 106125
|
||||
},
|
||||
"wx.js": {
|
||||
"raw": 74281,
|
||||
"gzip": 17356,
|
||||
"brotli": 16439
|
||||
},
|
||||
"wx-dom.js": {
|
||||
"raw": 63269,
|
||||
"gzip": 16857,
|
||||
"brotli": 15848
|
||||
},
|
||||
"images.tar.gz": {
|
||||
"raw": 5370904,
|
||||
"gzip": 5372562,
|
||||
"brotli": 5114985
|
||||
},
|
||||
"TOTAL": {
|
||||
"raw": 104647184,
|
||||
"gzip": 24993442,
|
||||
"brotli": 19426416
|
||||
}
|
||||
},
|
||||
"asyncify": {
|
||||
"kicad_editor.wasm": {
|
||||
"raw": 118557371,
|
||||
"gzip": 38473650,
|
||||
"brotli": 25666194
|
||||
},
|
||||
"kicad_editor.js": {
|
||||
"raw": 472325,
|
||||
"gzip": 116872,
|
||||
"brotli": 105662
|
||||
},
|
||||
"wx.js": {
|
||||
"raw": 74281,
|
||||
"gzip": 17356,
|
||||
"brotli": 16439
|
||||
},
|
||||
"wx-dom.js": {
|
||||
"raw": 63041,
|
||||
"gzip": 16773,
|
||||
"brotli": 15765
|
||||
},
|
||||
"images.tar.gz": {
|
||||
"raw": 5370145,
|
||||
"gzip": 5371803,
|
||||
"brotli": 5160999
|
||||
},
|
||||
"TOTAL": {
|
||||
"raw": 124537163,
|
||||
"gzip": 43996454,
|
||||
"brotli": 30965059
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{"arm":"asyncify","sha256":"6dab493196d060d7","when":"2026-08-14T09:46:53.665Z","section":"load","run":1,"loadMs":2629,"wasmFetch":{"durationMs":1053,"transferSize":32139352},"bootMem":{"wasmHeapBytes":556793856,"jsHeapBytes":27600000}}
|
||||
{"arm":"asyncify","sha256":"6dab493196d060d7","when":"2026-08-14T09:46:56.364Z","section":"load","run":2,"loadMs":2610,"wasmFetch":{"durationMs":961,"transferSize":32139352},"bootMem":{"wasmHeapBytes":556793856,"jsHeapBytes":27600000}}
|
||||
{"arm":"asyncify","sha256":"6dab493196d060d7","when":"2026-08-14T09:46:59.036Z","section":"load","run":3,"loadMs":2576,"wasmFetch":{"durationMs":989,"transferSize":32139352},"bootMem":{"wasmHeapBytes":556793856,"jsHeapBytes":27600000}}
|
||||
{"arm":"asyncify","sha256":"6dab493196d060d7","when":"2026-08-14T09:47:01.667Z","section":"load","run":4,"loadMs":2526,"wasmFetch":{"durationMs":987,"transferSize":32139352},"bootMem":{"wasmHeapBytes":556793856,"jsHeapBytes":27600000}}
|
||||
{"arm":"asyncify","sha256":"6dab493196d060d7","when":"2026-08-14T09:47:04.272Z","section":"load","run":5,"loadMs":2521,"wasmFetch":{"durationMs":997,"transferSize":32139352},"bootMem":{"wasmHeapBytes":556793856,"jsHeapBytes":27600000}}
|
||||
{"arm":"asyncify","sha256":"6dab493196d060d7","when":"2026-08-14T09:47:06.873Z","section":"load-cdp-sanity","loadMs":2514}
|
||||
{"arm":"asyncify","sha256":"6dab493196d060d7","when":"2026-08-14T09:47:09.937Z","section":"open-demo","run":1,"openMs":467,"postOpenMem":{"wasmHeapBytes":556793856,"jsHeapBytes":33100000}}
|
||||
{"arm":"asyncify","sha256":"6dab493196d060d7","when":"2026-08-14T09:47:12.982Z","section":"open-demo","run":2,"openMs":468,"postOpenMem":{"wasmHeapBytes":556793856,"jsHeapBytes":33100000}}
|
||||
{"arm":"asyncify","sha256":"6dab493196d060d7","when":"2026-08-14T09:47:16.025Z","section":"open-demo","run":3,"openMs":467,"postOpenMem":{"wasmHeapBytes":556793856,"jsHeapBytes":33100000}}
|
||||
{"arm":"asyncify","sha256":"6dab493196d060d7","when":"2026-08-14T09:47:25.126Z","section":"open-vme","run":1,"bytes":29032252,"openMs":6465,"openPeakHeap":962330624,"postOpenMem":{"wasmHeapBytes":962330624,"jsHeapBytes":56800000}}
|
||||
{"arm":"asyncify","sha256":"6dab493196d060d7","when":"2026-08-14T09:47:34.418Z","section":"open-vme","run":2,"bytes":29032252,"openMs":6582,"openPeakHeap":962330624,"postOpenMem":{"wasmHeapBytes":962330624,"jsHeapBytes":56800000}}
|
||||
{"arm":"asyncify","sha256":"6dab493196d060d7","when":"2026-08-14T09:47:43.512Z","section":"open-vme","run":3,"bytes":29032252,"openMs":6398,"openPeakHeap":1182793728,"postOpenMem":{"wasmHeapBytes":1182793728,"jsHeapBytes":56800000}}
|
||||
{"arm":"asyncify","sha256":"6dab493196d060d7","when":"2026-08-14T09:47:58.746Z","section":"fps-vme","throttle":1,"rep":1,"rafFps":44.5,"distinctFps":7.8}
|
||||
{"arm":"asyncify","sha256":"6dab493196d060d7","when":"2026-08-14T09:48:04.925Z","section":"fps-vme","throttle":1,"rep":2,"rafFps":49.1,"distinctFps":7.8}
|
||||
{"arm":"asyncify","sha256":"6dab493196d060d7","when":"2026-08-14T09:48:10.980Z","section":"fps-vme","throttle":4,"rep":1,"rafFps":32.4,"distinctFps":3}
|
||||
{"arm":"asyncify","sha256":"6dab493196d060d7","when":"2026-08-14T09:48:17.030Z","section":"fps-vme","throttle":4,"rep":2,"rafFps":37.6,"distinctFps":3.1}
|
||||
{"arm":"asyncify","sha256":"6dab493196d060d7","when":"2026-08-14T09:48:23.361Z","section":"fps-vme","throttle":6,"rep":1,"rafFps":39.9,"distinctFps":4.1}
|
||||
{"arm":"asyncify","sha256":"6dab493196d060d7","when":"2026-08-14T09:48:29.439Z","section":"fps-vme","throttle":6,"rep":2,"rafFps":34.3,"distinctFps":2.8}
|
||||
{"arm":"asyncify","sha256":"6dab493196d060d7","when":"2026-08-14T09:48:29.442Z","section":"fps-vme-postmem","postFpsMem":{"wasmHeapBytes":962330624,"jsHeapBytes":60300000}}
|
||||
{"arm":"asyncify","sha256":"6dab493196d060d7","when":"2026-08-14T09:48:46.855Z","section":"open-jetson","bytes":84806775,"outcome":"loaded","openMs":14584,"openPeakHeap":1904214016,"postOpenMem":{"wasmHeapBytes":1904214016,"jsHeapBytes":116000000}}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:43:04.217Z","section":"load","run":1,"loadMs":1841,"wasmFetch":{"durationMs":692,"transferSize":17051037},"bootMem":{"wasmHeapBytes":386662400,"jsHeapBytes":27600000}}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:43:05.422Z","section":"load","run":2,"loadMs":1117,"wasmFetch":{"durationMs":554,"transferSize":17051037},"bootMem":{"wasmHeapBytes":268435456,"jsHeapBytes":29400000}}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:43:07.469Z","section":"load","run":3,"loadMs":1547,"wasmFetch":{"durationMs":573,"transferSize":17051037},"bootMem":{"wasmHeapBytes":386662400,"jsHeapBytes":27600000}}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:43:09.081Z","section":"load","run":4,"loadMs":1123,"wasmFetch":{"durationMs":574,"transferSize":17051037},"bootMem":{"wasmHeapBytes":386662400,"jsHeapBytes":24500000}}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:43:10.697Z","section":"load","run":5,"loadMs":1538,"wasmFetch":{"durationMs":571,"transferSize":17051037},"bootMem":{"wasmHeapBytes":386662400,"jsHeapBytes":26000000}}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:43:11.887Z","section":"load-cdp-sanity","loadMs":1117}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:43:14.226Z","section":"open-demo","run":1,"openMs":698,"postOpenMem":{"wasmHeapBytes":386662400,"jsHeapBytes":31200000}}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:43:16.208Z","section":"open-demo","run":2,"openMs":693,"postOpenMem":{"wasmHeapBytes":386662400,"jsHeapBytes":31200000}}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:43:18.164Z","section":"open-demo","run":3,"openMs":358,"postOpenMem":{"wasmHeapBytes":386662400,"jsHeapBytes":31200000}}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:43:23.447Z","section":"open-vme","run":1,"bytes":29032252,"openMs":3551,"openPeakHeap":801898496,"postOpenMem":{"wasmHeapBytes":801898496,"jsHeapBytes":56800000}}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:43:28.639Z","section":"open-vme","run":2,"bytes":29032252,"openMs":3538,"openPeakHeap":801898496,"postOpenMem":{"wasmHeapBytes":801898496,"jsHeapBytes":60300000}}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:43:33.963Z","section":"open-vme","run":3,"bytes":29032252,"openMs":3635,"openPeakHeap":801898496,"postOpenMem":{"wasmHeapBytes":801898496,"jsHeapBytes":60300000}}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:43:45.276Z","section":"fps-vme","throttle":1,"rep":1,"rafFps":51.6,"distinctFps":1.6,"note":"SUPERSEDED - sampler watched #canvas, not glcanvas"}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:43:51.293Z","section":"fps-vme","throttle":1,"rep":2,"rafFps":53,"distinctFps":1.8,"note":"SUPERSEDED - sampler watched #canvas, not glcanvas"}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:43:57.442Z","section":"fps-vme","throttle":4,"rep":1,"rafFps":36,"distinctFps":0.7,"note":"SUPERSEDED - sampler watched #canvas, not glcanvas"}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:44:03.520Z","section":"fps-vme","throttle":4,"rep":2,"rafFps":36.2,"distinctFps":0,"note":"SUPERSEDED - sampler watched #canvas, not glcanvas"}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:44:09.587Z","section":"fps-vme","throttle":6,"rep":1,"rafFps":33,"distinctFps":0,"note":"SUPERSEDED - sampler watched #canvas, not glcanvas"}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:44:16.055Z","section":"fps-vme","throttle":6,"rep":2,"rafFps":33.7,"distinctFps":0,"note":"SUPERSEDED - sampler watched #canvas, not glcanvas"}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:44:16.057Z","section":"fps-vme-postmem","postFpsMem":{"wasmHeapBytes":801898496,"jsHeapBytes":60300000}}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:44:26.524Z","section":"open-jetson","bytes":84806775,"outcome":"loaded","openMs":8620,"openPeakHeap":1730805760,"postOpenMem":{"wasmHeapBytes":1730805760,"jsHeapBytes":116000000}}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:52:00.000Z","section":"fps-vme","throttle":1,"rep":1,"rafFps":51.9,"distinctFps":9.6,"note":"glcanvas sampler re-run; when approximate (row reconstructed from run log after test-results wipe)"}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:52:06.000Z","section":"fps-vme","throttle":1,"rep":2,"rafFps":55.7,"distinctFps":9.2,"note":"glcanvas sampler re-run; when approximate"}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:52:12.000Z","section":"fps-vme","throttle":4,"rep":1,"rafFps":39.2,"distinctFps":5.1,"note":"glcanvas sampler re-run; when approximate"}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:52:18.000Z","section":"fps-vme","throttle":4,"rep":2,"rafFps":41.4,"distinctFps":5.2,"note":"glcanvas sampler re-run; when approximate"}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:52:24.000Z","section":"fps-vme","throttle":6,"rep":1,"rafFps":34.3,"distinctFps":3.3,"note":"glcanvas sampler re-run; when approximate"}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:52:30.000Z","section":"fps-vme","throttle":6,"rep":2,"rafFps":41.8,"distinctFps":5.3,"note":"glcanvas sampler re-run; when approximate"}
|
||||
{"arm":"jspi","sha256":"6f03e62d6d5619c3","when":"2026-08-14T09:52:30.100Z","section":"fps-vme-postmem","postFpsMem":{"wasmHeapBytes":801898496,"jsHeapBytes":64000000}}
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
# JSPI vs Asyncify: editor benchmark (2026-08-14)
|
||||
|
||||
A/B benchmark of the `kicad_editor` app built two ways:
|
||||
|
||||
- **Arm A — asyncify**: `pcbjam` @ `29c61b8` (branch `staging`), emscripten **4.0.2**,
|
||||
`-sASYNCIFY=1` + host post-link pipeline (finalize → hoist-cpp-catches →
|
||||
`wasm-opt --asyncify` → `wasm-opt -O1`, Binaryen fork v130).
|
||||
- **Arm B — JSPI**: worktree @ `9c475a8` (branch `experiment/jspi`), emscripten **6.0.6**,
|
||||
`-sJSPI`, single-phase link, no post-link transform (only the ~1 s env-shim patch).
|
||||
|
||||
**Stated confound, up front:** the two arms differ by emscripten major (4.0.2 vs
|
||||
6.0.6) — libc/libc++/linker/JS-glue all moved with it. That skew is inseparable
|
||||
from the backend switch (strict-JSPI needs emscripten 6); every number below is
|
||||
"the shipped asyncify build vs the shipped JSPI build", not a pure
|
||||
suspension-mechanism isolate. What IS matched: same KiCad commit (`4bfed3f1`),
|
||||
wxWidgets 3.3.1, EH flags (`-fwasm-exceptions -sSUPPORT_LONGJMP=wasm
|
||||
-sWASM_LEGACY_EXCEPTIONS=1`), mimalloc, memory config (256 MB initial / 4 GB
|
||||
max), **DEBUG mode on both** (TU `-g -O1`, link `-O1 -g -gseparate-dwarf` — the
|
||||
default), docker caps 10 CPU / 32 GB, `-j 10`, `BINARYEN_CORES=8`,
|
||||
`HOIST_KEEP_NAMES` unset, runs serialized on an idle machine.
|
||||
|
||||
Machine: Apple M4 Max, 16 cores, 64 GB; Docker Desktop VM 12 CPU / 28 GiB;
|
||||
macOS 26.5.2. Raw data: [`bench-data-2026-08/`](bench-data-2026-08/).
|
||||
|
||||
## TL;DR
|
||||
|
||||
| Metric | Asyncify | JSPI | Δ |
|
||||
|---|---|---|---|
|
||||
| Editor wasm, raw | 113.1 MB | 94.1 MB | **−17 %** |
|
||||
| Editor wasm, gzip −9 | 36.7 MB | 18.6 MB | **−49 %** |
|
||||
| Editor wasm, brotli | 24.5 MB | 13.5 MB | **−45 %** |
|
||||
| Post-link tail (per build!) | 63.4 s, 6.1 GB RSS | 1.6 s, 49 MB RSS | **−97 %** |
|
||||
| Clean rebuild (warm ccache) | 165 s | 95 s | **−42 %** |
|
||||
| Cold load (median of 5) | 2 576 ms | 1 538 ms | **−40 %** |
|
||||
| Open vme-wren (27.7 MB board) | 6 465 ms | 3 551 ms | **−45 %** |
|
||||
| Real redraws/s @1× (vme-wren) | 7.8 | 9.4 | **+21 %** |
|
||||
| Real redraws/s @4× throttle | 3.1 | 5.2 | **+68 %** |
|
||||
| Wasm heap after big-board open | 962 MB | 802 MB | **−17 %** |
|
||||
| Jetson 80.9 MB board open | 14.6 s, peak 1.90 GB | 8.6 s, peak 1.73 GB | **−41 %** |
|
||||
|
||||
The asyncify tax was real on every axis: bytes (instrumented code also
|
||||
compresses ~2× worse), build time (a mandatory 6-GB-RSS host pass per link),
|
||||
load (bigger download + more code to tier), compute (slower opens, fewer real
|
||||
frames — the gap *widens* under CPU throttle, the doc-12 signature of
|
||||
per-operation overhead rather than mere size), and memory.
|
||||
|
||||
## 1. Build time
|
||||
|
||||
Scenarios (identical commands both arms, run serially): **S1** no-change
|
||||
rebuild (`./docker/build.sh kicad_editor -j 10` twice, 2nd reported), **S2**
|
||||
clean editor rebuild with warm ccache (`--clean-kicad`, twice), **S3** clean
|
||||
rebuild with `CCACHE_DISABLE=1` (manual `compose exec`, compile cost only).
|
||||
|
||||
Both trees exhibit an **mtime ping-pong** in the wx re-enter (a reconfigure
|
||||
regenerates files the next rsync restores → every other build pays a wx
|
||||
reconfigure). Numbers are therefore given per observed mode; phase splits from
|
||||
the `@KW@` markers.
|
||||
|
||||
| Scenario | Asyncify | JSPI |
|
||||
|---|---|---|
|
||||
| S1 no-change, quiet mode | **175 s** (cfg 40 + link 40 + finalize 4 + **asyncify 58**) | **154 s** (cfg 56 + link 46, tail ≈0) |
|
||||
| S1 no-change, wx-churn mode | 427 s (wx 89 + cfg 132 + compile 124 + tail 63) | 379 s (first run after container recreate) |
|
||||
| S2 clean, warm ccache, quiet | **165 s** (1 819 TU in 29 s + tail 61) | **95 s** (1 819 TU in 26 s) |
|
||||
| S2 clean, wx-churn mode | 248 s | 122 s |
|
||||
| S3 clean, no ccache (kicad only) | **365 s** (cfg 154 + compile 211) | **304 s** (cfg 136 + compile 168) |
|
||||
| Container compile-only, steady | 91 s | — (contained in S2) |
|
||||
|
||||
Reading: the container-side compile is broadly comparable (emscripten 6 is
|
||||
~15-20 % faster on the uncached compile); **the structural difference is the
|
||||
host tail** — asyncify pays 58-63 s *on every single link*, JSPI pays ~1.6 s.
|
||||
In the dev loop that is the difference between a ~2.5-minute and a ~4-minute
|
||||
edit-to-browser cycle (or 7 min when the wx churn mode hits, which the tail
|
||||
compounds).
|
||||
|
||||
Note: A-S3's wall figure included a 453 s wx rebuild triggered by the bench's
|
||||
own `touch` of the wx build Makefile (used to pin the wx skip path) — excluded
|
||||
above; the kicad configure+compile figures are uncontaminated.
|
||||
|
||||
## 2. Build memory
|
||||
|
||||
- **Container peak** (cgroup v2 `memory.peak`, fresh cgroup per run): both arms
|
||||
18–24 GiB across all scenarios — dominated by the `-j 10` compile and the
|
||||
DWARF-heavy `wasm-ld` link, essentially backend-independent.
|
||||
- **Host post-link tail** (`/usr/bin/time -l`):
|
||||
- Asyncify: **63.4 s wall, 307 s user, peak RSS 6.09 GiB**
|
||||
(finalize ≈4 s + hoist+asyncify+`-O1` ≈58 s at `BINARYEN_CORES=8`).
|
||||
Historical logs show 59–154 s and one OOM-killed build (exit 137).
|
||||
- JSPI: **1.6 s wall, peak RSS 49 MB** (node env-shim patch).
|
||||
|
||||
## 3. Bundle size
|
||||
|
||||
Same-day builds, DEBUG mode, complete cold-load set
|
||||
(`kicad_editor.{wasm,js}`, `wx.js`, `wx-dom.js`, `images.tar.gz`):
|
||||
|
||||
| | raw | gzip −9 | brotli |
|
||||
|---|---|---|---|
|
||||
| Asyncify wasm | 118 557 371 B (113.1 MB) | 36.7 MB | 24.5 MB |
|
||||
| JSPI wasm | 98 664 038 B (94.1 MB) | 18.6 MB | 13.5 MB |
|
||||
| Asyncify total set | 118.8 MB | 42.0 MB | 29.5 MB |
|
||||
| JSPI total set | 99.8 MB | 23.8 MB | 18.5 MB |
|
||||
|
||||
The compressed delta (−49 % gzip) is much larger than the raw delta (−17 %):
|
||||
asyncify's spilled-locals/branch-table instrumentation is high-entropy. The
|
||||
over-the-wire cost of a cold editor load roughly **halves** under JSPI.
|
||||
Side note: separate DWARF is 2.27 GB vs 1.91 GB (−16 %); dev-tools-only fetch.
|
||||
|
||||
Other apps (existing artifacts, not rebuilt today): pl_editor −19 %,
|
||||
gerbview −19 %, calculator −26 %, occ_service −1 % (never asyncified),
|
||||
ngspice_service 0 %.
|
||||
|
||||
## 4. Runtime
|
||||
|
||||
One harness for both arms: the jspi tree's Playwright 1.62.1 `perf` project
|
||||
(bundled Chromium, no JSPI-specific launch flags), `tests/kicad/
|
||||
pcbnew-large-perf.spec.ts`, artifact sets swapped in `tests/apps/kicad/` with
|
||||
sha256 recorded per row (jspi `6f03e62d…`, asyncify `6dab4931…`). The harness
|
||||
page seeds KiCad config for both settings versions (9.99 + 10.0) so the
|
||||
asyncify binary boots wizard-free. Local static server serves wasm gzipped
|
||||
(hence `transferSize` ≈ the gzip column above). Headless; per doc-12, headless
|
||||
FPS is a comparative indicator, not an absolute.
|
||||
|
||||
**Cold load** (fresh context ×5, navigation → fully-ready editor):
|
||||
|
||||
| | Asyncify | JSPI |
|
||||
|---|---|---|
|
||||
| loadMs median (range) | 2 576 (2 521–2 629) | 1 538 (1 117–1 841) |
|
||||
| wasm fetch (gzipped wire) | ~990 ms / 32.1 MB | ~570 ms / 17.1 MB |
|
||||
| wasm heap at boot | 556.8 MB | 386.7 MB |
|
||||
|
||||
CDP-attach sanity: loadMs with a CDP session pre-attached fell inside each
|
||||
arm's normal range (2 514 / 1 117 ms) — no tier-down artifact from the
|
||||
throttling channel.
|
||||
|
||||
**Board open** (`Module.kicadOpenFile`, ×3 each):
|
||||
|
||||
| Board | Asyncify | JSPI |
|
||||
|---|---|---|
|
||||
| demo (155 KB, 15 fp) | 467 ms | 358–698 ms (par — too small to discriminate) |
|
||||
| vme-wren (27.7 MB, 1 508 fp, 24 858 seg) | **6 465 ms** median, peak heap 962–1 183 MB | **3 551 ms** median, peak heap 802 MB |
|
||||
| jetson-agx-thor (80.9 MB, 1 125 fp) | loaded, **14 584 ms**, peak 1.90 GB | loaded, **8 620 ms**, peak 1.73 GB |
|
||||
|
||||
Neither arm OOMs even on the 80.9 MB board — both stay well under the 4 GB cap.
|
||||
|
||||
**Interaction FPS** on vme-wren (Lissajous pan + wheel zoom, 6 s × 2 reps;
|
||||
`rafFps` = main-thread rAF; `distinctFps` = distinct GAL glcanvas frames at a
|
||||
~30 Hz pixel-hash sampler — the honest "real redraws" number, since rAF keeps
|
||||
vsync-ticking when the GAL skips):
|
||||
|
||||
| Throttle | Asyncify raf / distinct | JSPI raf / distinct |
|
||||
|---|---|---|
|
||||
| 1× | 44.5–49.1 / **7.8** | 51.9–55.7 / **9.2–9.6** |
|
||||
| 4× | 32.4–37.6 / **3.0–3.1** | 39.2–41.4 / **5.1–5.2** |
|
||||
| 6× | 34.3–39.9 / **2.8–4.1** | 34.3–41.8 / **3.3–5.3** |
|
||||
|
||||
The distinct-frame gap grows from +21 % at 1× to +68 % at 4× — the same
|
||||
"advantage widens under throttle" signature doc-12 used to prove lower
|
||||
CPU-per-operation (as opposed to just a smaller module).
|
||||
|
||||
**Memory checkpoints** (wasm linear memory; Chromium `usedJSHeapSize` tracked
|
||||
alongside, differences <10 %): boot 557 vs 387 MB; after vme-wren open 962 vs
|
||||
802 MB; unchanged after the FPS sweep on both arms.
|
||||
|
||||
## 5. Methodology notes & gotchas
|
||||
|
||||
- Asyncify arm reused the warm `kicad-wasm-main` compose volume (62 GB); its
|
||||
branch-default `kicad-wasm-staging` volume is a cold stub — running without
|
||||
`COMPOSE_PROJECT_NAME=kicad-wasm-main` would have benchmarked a multi-hour
|
||||
cold dep build. The other session's tree was verified idle before/after and
|
||||
`git status` byte-identical.
|
||||
- Container `memory.peak` read from a per-run fresh cgroup (`compose stop`
|
||||
between runs, `KICAD_KEEP_CONTAINER=1` so the read happens before teardown),
|
||||
plus a 5 s `docker stats` sampler (CSV in bench-data).
|
||||
- The first S3 attempt died in wx's PCRE `aclocal` regen (manual `compose
|
||||
exec` skips build.sh's rsync, which perturbs the wx reconfigure check); fixed
|
||||
by pre-touching the wx build Makefile — which on arm A then triggered the wx
|
||||
rebuild noted in §1. Symmetric procedure both arms.
|
||||
- The jspi-arm FPS rows in the archived ndjson appear twice: the first pass
|
||||
sampled the static `#canvas` (distinct ≈ 0, marked SUPERSEDED); the glcanvas
|
||||
re-run is authoritative and matches what `perf-utils.measureFpsDetailed` now
|
||||
does. Arm A ran entirely with the fixed sampler.
|
||||
- Playwright clears `tests/test-results/` per invocation — it ate the arm B
|
||||
ndjson during the arm A run (reconstructed from the run logs, two timestamps
|
||||
approximate). The spec now writes to `tests/bench-results/` instead.
|
||||
- The live dev app at :3048 was kept on the JSPI build throughout (its
|
||||
`public/wasm` symlink pointed at a stash during the swap window) and
|
||||
verified serving the JSPI wasm afterwards.
|
||||
|
||||
## 6. Reproduce
|
||||
|
||||
```bash
|
||||
# build side (each arm, serialized; see §1 for scenario commands)
|
||||
KICAD_NO_MONITOR=1 KICAD_KEEP_CONTAINER=1 ./docker/build.sh kicad_editor -j 10
|
||||
|
||||
# runtime side (from tests/, artifacts staged in tests/apps/kicad/)
|
||||
cp kicad/qa/data/pcbnew/vme-wren.kicad_pcb tests/apps/kicad/board/
|
||||
cp kicad/demos/jetson-agx-thor-baseboard/jetson-agx-thor-baseboard.kicad_pcb tests/apps/kicad/board/
|
||||
BENCH_ARM=<arm> PERF_LARGE=1 npx playwright test --project=perf pcbnew-large-perf.spec.ts --workers=1
|
||||
# → tests/bench-results/perf-bench-<arm>.ndjson
|
||||
```
|
||||
Loading…
Reference in a new issue