next.js
c8e811ee - bench: add warm cached build benchmark to nested-deps-app-router-many-pages (#100570)

Commit
2 days ago
bench: add warm cached build benchmark to nested-deps-app-router-many-pages (#100570) ### What? Adds `bench-warm-build.mjs` (plus a `bench-warm-build` script entry) to `bench/nested-deps-app-router-many-pages`. It benchmarks warm Turbopack compiles of `next build` with the persistent filesystem cache, after changing N source files. ```bash cd bench/nested-deps-app-router-many-pages pnpm prepare-bench node bench-warm-build.mjs --files=1000 ``` ### Why? To measure the incremental rebuild work of a cached build after many changes on a large app, e.g. for turbo-tasks scheduler changes like #100561. Until now there was no easy way to do that. ### How? - **Cold compile:** an optional cold compile (deletes `.next`) fills the cache. It is reported but not counted. - **Warm compiles:** by default, 20 warm compiles run (`--iterations=20`). The first 5 are warm-up (`--warmup=5`): they are printed but excluded from the statistics. - Each warm iteration changes N component files from `components/`, picked deterministically with a seeded shuffle. - The change is a rendered JSX attribute with a per-run/per-iteration value (digits only), so it changes the emitted code (not just a comment) and always differs from what is cached. A file that is still changed from an earlier, killed run is changed again rather than failing. - **Statistics:** wall time and the "Compiled successfully in" time are printed per iteration. Over the non-warm-up builds, the script prints count, median, mean, sample standard deviation, min and max. - **Compile only:** builds use `--experimental-build-mode=compile`, so prerendering the 3,000 routes doesn't add noise. - **Cache safety:** the build cache is enabled by default, so the app config is unchanged. The script sets `TURBO_ENGINE_IGNORE_DIRTY=1`, because a native binary built locally from a dirty checkout otherwise disables the cache. It also fails if the build reports a disabled cache. - **Restoring files:** changed files are restored after success, failure, and SIGINT/SIGTERM. On a signal, it waits for the running build to exit before restoring. - **Options:** strict validation of `--files`, `--iterations`, `--warmup` (must be smaller than `--iterations`), `--cold` and `--seed`. ### Results Example output of this script with the default options (20 warm builds, the first 5 excluded) for the native binary of this PR's base commit (canary). Times are in seconds. "Compile" is the "Compiled successfully in" time and "wall" is the whole `next build` process. | | cold compile | cold wall | warm compile median (mean ± std dev, min–max) | warm wall median (mean ± std dev, min–max) | |---|---|---|---|---| | `--files=0` | 23.6 | 33.4 | 7.5 (7.44 ± 0.17, 7.1–7.7) | 12.9 (12.86 ± 0.60, 11.9–13.9) | | `--files=1000` | 24.9 | 37.8 | 8.6 (8.54 ± 0.17, 8.2–8.7) | 14.6 (14.51 ± 0.63, 13.2–15.5) | - **Warm builds without changes:** a warm build takes about a third of the cold compile time, even with no files changed (7.5 s vs 23.6 s). - **1,000 changed components** add about 1.1 s of compile time on top of that. - **First warm build:** it is noticeably slower than the rest (14.2 s compile with 1,000 files, 10.9 s with 0), so it is a warm-up build. - **Setup:** - Machine: 32 logical CPUs (Intel Xeon @ 2.90 GHz), 64 GiB RAM, Linux, Node.js 24. - App: the generated app with 3,000 pages and 3,020 components (`pnpm prepare-bench`). - Native binary: a locally built release build. - **Caveats:** one machine and one generated app; the numbers vary between runs and machines, so compare binaries on the same machine. ### Verification - Ran end to end with locally built release native bindings on a 32-core machine: - cold + warm compiles, including the default 20/5 runs; - the changed marker appears in the emitted chunks; - the generated components are byte-identical afterwards, also after a SIGINT during a build, with no leftover build process. - The cache-disabled guard fails as expected when the ignore-dirty override is removed and the binary was built from a dirty tree. - Invalid options are rejected before any build: unknown, `__proto__`, unsafe or non-integer values, `--iterations=0`, bad booleans, and `--warmup` that is negative or not smaller than `--iterations`. - The change regex matches both an unchanged `<div>` and one that was already changed. - prettier and eslint pass. <!-- NEXT_JS_LLM --> <!-- fleet 519ddd31-d876-47cd-9933-de3e7be9444a --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Author
Parents
Loading