Say why a run is slow: per-stage timing, and a JIT check - #263
Conversation
The deployed site runs the same bracket roughly twelve times slower than the same build served locally, in the same browser on the same machine, and no amount of reading could settle why. Wall-clock timestamps in the run console show that a stage was slow but not what it was doing, and the candidates are not close relatives: fetching a module and compiling it are the host's problem, computing is the build's. Three hypotheses have already been argued from those timestamps and all three were wrong. Each tool invocation is now timed in six parts -- fetch and evaluate the glue, fetch and compile the .wasm, instantiate, stage inputs, callMain, collect outputs -- and reports itself only when the total passes a second. A healthy run is silent, because a whole 18-frame merge is around fifteen seconds and no single invocation should approach that. Any line at all is a stage worth reading. The filter stage reports its own duration too. It runs no WebAssembly, only createImageBitmap per frame, so whether it slows down alongside everything else is what distinguishes a WebAssembly problem from something underneath both. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
This instrumentation did its job on the first run, so recording the outcome here. The deployed-site log with it enabled: Fetch and compile cost under a second per module and zero once cached. Execution is the entire loss. That ruled out delivery, The A pure-JavaScript loop with no network and no wasm then settled it:
11x, matching the pipeline's ratios. The tab was getting a fraction of the CPU; the deployment, the WebAssembly build and #260 are all exonerated. Worth merging on its own merits: four hypotheses were argued from wall-clock timestamps before this existed and every one of them was wrong. |
Edge's "Enhance your security on the web" disables the JavaScript JIT for sites it does not consider familiar, and a freshly deployed URL never is. Everything then runs interpreted, WebAssembly included. The reference bracket took 6m32s instead of 30s, with no error, nothing different about the page, and the same build served from localhost unaffected -- because a site you open every day stays on that feature's allowlist. That is indistinguishable from "this tool is slow", and it was read that way for most of a day. Four explanations were argued from wall-clock timestamps first: hdrgen being slow, the wasm download, compilation, and DevTools being attached. All four were wrong. A twenty-millisecond arithmetic loop would have found it immediately, so the run now starts with one. The threshold errs towards firing. Nothing in JavaScript distinguishes a disabled JIT from a slow device, and the message is true either way, so the wording reports what was measured and offers the usual cause rather than asserting it. For scale: 32 ms with a JIT here, 271 ms without. Also records it in DEPLOYMENT.md, next to the other things about this build that are surprising rather than wrong. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A local build regenerated src-tauri/gen/schemas/ and `git add -A` swept the result into the timing commit. Nothing here is part of that change, and a diff that carries unrelated generated files makes a reviewer check whether they matter. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t just Edge The warning and the deployment note both read as an Edge quirk. It is not one. Chromium browsers have had a per-site control since 122, Safari does it as part of Lockdown Mode with no way to separate the two, Firefox does it through about:config, and Tor Browser does it at "Safer". Managed machines can arrive there without anyone choosing it, since the CIS benchmark for Edge recommends disabling JIT at Level 2. Edge stays the worked example because its rule is what made this so hard to read: on Balanced it keeps optimisation for sites visited often, which exempts localhost while catching a newly deployed URL, and that difference looks exactly like a hosting problem. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(pipeline): say where a slow stage spent its time The deployed site runs the same bracket roughly twelve times slower than the same build served locally, in the same browser on the same machine, and no amount of reading could settle why. Wall-clock timestamps in the run console show that a stage was slow but not what it was doing, and the candidates are not close relatives: fetching a module and compiling it are the host's problem, computing is the build's. Three hypotheses have already been argued from those timestamps and all three were wrong. Each tool invocation is now timed in six parts -- fetch and evaluate the glue, fetch and compile the .wasm, instantiate, stage inputs, callMain, collect outputs -- and reports itself only when the total passes a second. A healthy run is silent, because a whole 18-frame merge is around fifteen seconds and no single invocation should approach that. Any line at all is a stage worth reading. The filter stage reports its own duration too. It runs no WebAssembly, only createImageBitmap per frame, so whether it slows down alongside everything else is what distinguishes a WebAssembly problem from something underneath both. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(pipeline): say so when the browser is running without a JIT Edge's "Enhance your security on the web" disables the JavaScript JIT for sites it does not consider familiar, and a freshly deployed URL never is. Everything then runs interpreted, WebAssembly included. The reference bracket took 6m32s instead of 30s, with no error, nothing different about the page, and the same build served from localhost unaffected -- because a site you open every day stays on that feature's allowlist. That is indistinguishable from "this tool is slow", and it was read that way for most of a day. Four explanations were argued from wall-clock timestamps first: hdrgen being slow, the wasm download, compilation, and DevTools being attached. All four were wrong. A twenty-millisecond arithmetic loop would have found it immediately, so the run now starts with one. The threshold errs towards firing. Nothing in JavaScript distinguishes a disabled JIT from a slow device, and the message is true either way, so the wording reports what was measured and offers the usual cause rather than asserting it. For scale: 32 ms with a JIT here, 271 ms without. Also records it in DEPLOYMENT.md, next to the other things about this build that are surprising rather than wrong. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: drop generated Tauri schema churn from this branch A local build regenerated src-tauri/gen/schemas/ and `git add -A` swept the result into the timing commit. Nothing here is part of that change, and a diff that carries unrelated generated files makes a reviewer check whether they matter. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(pipeline): name every browser that can turn optimisation off, not just Edge The warning and the deployment note both read as an Edge quirk. It is not one. Chromium browsers have had a per-site control since 122, Safari does it as part of Lockdown Mode with no way to separate the two, Firefox does it through about:config, and Tor Browser does it at "Safer". Managed machines can arrive there without anyone choosing it, since the CIS benchmark for Edge recommends disabling JIT at Level 2. Edge stays the worked example because its rule is what made this so hard to read: on Balanced it keeps optimisation for sites visited often, which exempts localhost while catching a newly deployed URL, and that difference looks exactly like a hosting problem. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The deployed site ran the reference bracket in 6m32s against 30s for the same build served locally, in the same browser on the same machine. This PR is what found the cause, plus the check that stops it happening to anyone else.
The cause
Microsoft Edge's "Enhance your security on the web" disables the JavaScript JIT for sites it does not consider familiar, and a freshly deployed URL never is. Everything then runs interpreted, WebAssembly included.
localhostwas unaffected because a site you open every day stays on that feature's allowlist — which is precisely what makes it read as "the deployment is slow".Confirmed by disabling the setting: full speed returns.
Nothing was wrong with the deployment, the WebAssembly build, the worker, or #260.
Why this needed instrumentation
Four explanations were argued from wall-clock timestamps before anything was measured, and every one was wrong:
The first instrumented run ended it in one reading:
Fetch and compile under a second; execution the entire loss. And the filter stage — which runs no WebAssembly at all — was slow too, which pointed away from wasm entirely. A pure-JS loop then settled it: 2736 ms on the deployed tab against 247 ms locally, on a fresh tab with nothing loaded.
What this adds
Per-tool timing. Each invocation is timed in six parts (fetch/evaluate glue, fetch/compile wasm, instantiate, stage,
callMain, collect) and reports itself only when its total passes one second. A healthy run stays silent, so any line at all is worth reading. This is what distinguishes a hosting problem from a build problem, and it cannot be recovered from timestamps afterwards.The filter stage reports its own duration. It runs no WebAssembly, so whether it slows down alongside everything else separates "wasm is slow" from "everything is slow".
A JIT check at the start of each run. A ~30 ms arithmetic loop; if the engine is far below what a working JIT delivers, the run console says so and names the setting. The threshold errs towards firing: nothing in JavaScript distinguishes a disabled JIT from a slow device, and the message is true either way, so it reports what was measured and offers the usual cause rather than asserting it. 32 ms with a JIT here, 271 ms without.
DEPLOYMENT.mdrecords the whole thing under known limits.Verification
431 tests,
tscclean, lint clean, full 18-frame browser pipeline passing end to end in 32.6s with all of it in place. The calibration loop costs 32 ms against a run measured in tens of seconds.🤖 Generated with Claude Code