Repository navigation
feat: run walltime benchmarks in forks - #101
not-matthias wants to merge 10 commits into
Conversation
a33e439 to
4bd0611
Compare
Merging this PR will regress 1 benchmark4 warnings, 1 note
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
[Medium impact] The change affects walltime measurement across the tinybench and Vitest 5 integrations. The PR still needs the previously reported fix that keeps Tinybench results and
|
860d65c to
8038822
Compare
8038822 to
86bdbbb
Compare
86bdbbb to
e9af7a1
Compare
adriencaccia
left a comment
There was a problem hiding this comment.
Main question is cost: with 3 forks by default, this repo's walltime benchmark step goes from 351 s on main to 1408 s on this PR. A run with forks should take about as long as one without; see the threads on the budget per fork, the per-fork overhead and the sample count of fast tinybench benchmarks. Vitest hooks must also keep working as they do today.
c376bfd to
3dca851
Compare
f4d1b47 to
f086cdf
Compare
adriencaccia
left a comment
There was a problem hiding this comment.
Earlier threads are addressed, or deferred to after the beta as agreed on COD-3960. Two Vitest issues come with the fork reporter (inline), and Greptile's bench.table() thread holds: in the walltime job on main every bench.table() prints its results, here every parent prints a table of nulls and each fork a table with only its own row.
d2701ee to
5a0c4db
Compare
Benchmarks in one process share allocator and JIT state, and rounds within one process are correlated, so a single process gives a biased view of a benchmark. Add the building blocks for integrations to run each walltime benchmark in several fresh processes (forks) and pool their rounds: - `walltimeIsolation(forks?)` picks the mode from the env: parent, fork (`CODSPEED_ISOLATED_BENCHMARK`), or one shared process when `CODSPEED_PROCESS_ISOLATION=false` - the fork count is `CODSPEED_WALLTIME_FORKS`, else the integration's `forks` option, else 3 - `runForks`/`runForksSync` re-run the benchmark command (keeping the V8 flags) once per fork and collect the samples each fork writes with `writeForkSamples`; a failing fork's exit code is passed on - `samplesToResult` builds tinybench-style statistics over the pooled samples Refs COD-3960
`withCodSpeed(bench, { forks })` sets the fork count of walltime runs, and
`CODSPEED_WALLTIME_FORKS` overrides it. The parent runs each task in its
forks and reports their pooled rounds; a fork runs only its task, hands its
samples over and prints no banner or summary. Simulation runs are unchanged.
Refs COD-3960
With Vitest 5, each walltime benchmark runs in fresh `vitest bench`
processes restricted to its file. The fork count resolves as
`CODSPEED_WALLTIME_FORKS`, the benchmark's or run's `forks` option,
`codspeedPlugin({ forks })`, then 3. The test timeout is scaled by the
env or plugin fork count, since every fork of a test runs within it.
Vitest's own report shows zeroed statistics for forked benchmarks; the
CodSpeed results hold the pooled rounds. Vitest 3/4 don't fork.
Closes COD-3960
5a0c4db to
d172321
Compare
TLDR: Rounds within one process are correlated (JIT tier-up, heap sizing, allocator state), so walltime results depend on the process they ran in. Each walltime benchmark now runs in 3 fresh processes (forks) by default, and their rounds are pooled into one result (tinybench and Vitest 5).
Fork count, highest priority first:
CODSPEED_WALLTIME_FORKS=<n>bench(name, { forks }, fn)orbench.compare(..., { forks })withCodSpeed(bench, { forks }),codspeedPlugin({ forks })3CODSPEED_PROCESS_ISOLATION=falseruns everything in one process.How it works
execPath+execArgv+argv, so the runner's V8 flags survive) once per fork, withCODSPEED_ISOLATED_BENCHMARK=<uri>. The fork measures only that benchmark and writes its samples to a temp file. The parent pools them and writes the walltime result.Cost (
examples/with-typescript-esmtinybench, 9 benchmarks): 77 s with isolation off, 140 s with 1 fork, 412 s with 3 forks. Each fork pays process startup.Review notes
forksabove that count can still time out.BenchOptionstype doesn't includeforks, so the per-benchmark option needs a cast in TypeScript.Closes COD-3960