Skip to content

feat: run walltime benchmarks in forks - #101

Open
not-matthias wants to merge 10 commits into
mainfrom
cod-3960-implement-process-isolation-in-codspeed-node
Open

not-matthias wants to merge 10 commits into
mainfrom
cod-3960-implement-process-isolation-in-codspeed-node

Conversation

@not-matthias

@not-matthias not-matthias commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

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:

  1. CODSPEED_WALLTIME_FORKS=<n>
  2. Vitest 5 per-benchmark: bench(name, { forks }, fn) or bench.compare(..., { forks })
  3. Integration option: withCodSpeed(bench, { forks }), codspeedPlugin({ forks })
  4. 3

CODSPEED_PROCESS_ISOLATION=false runs everything in one process.

How it works

  • The parent re-runs the benchmark command (execPath + execArgv + argv, so the runner's V8 flags survive) once per fork, with CODSPEED_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.
  • Vitest workers spawn the main process's command, and each fork narrows benchmark discovery to the benchmark's file.

Cost (examples/with-typescript-esm tinybench, 9 benchmarks): 77 s with isolation off, 140 s with 1 fork, 412 s with 3 forks. Each fork pays process startup.

Review notes

  • Simulation/memory runs, benchmark.js and Vitest 3/4 don't fork.
  • Vitest 5: its own report shows zeroed statistics for forked benchmarks; the CodSpeed results hold the pooled rounds. The test timeout is multiplied by the env or plugin fork count. A per-benchmark forks above that count can still time out.
  • Vitest's BenchOptions type doesn't include forks, so the per-benchmark option needs a cast in TypeScript.

Closes COD-3960

@not-matthias
not-matthias force-pushed the cod-3960-implement-process-isolation-in-codspeed-node branch from a33e439 to 4bd0611 Compare October 21, 2026 10:14
@codspeed

codspeed Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Merging this PR will regress 1 benchmark

4 warnings, 1 note

⚠️ Walltime results may be unreliable

Some benchmarks used the Walltime instrument outside CodSpeed Macro Runners, which can make the results inconsistent.

Learn more about Macro Runners

See affected benchmarks

⚠️ 14 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

⚠️ 1 benchmark with a significant change ran on different hardware

Its base and head results were measured on machines with a different CPU, core count or memory, so the change might not come from the code. Run base and head on the same runner type.

Learn more about Macro Runners

See affected benchmarks

⚠️ 7 benchmarks with a significant change ran in a different software environment

Their base and head results were measured with a different OS or toolchain, such as compiler, runtime or library versions, so the changes might come from this difference rather than from the code. Open an affected benchmark to see what changed.

See affected benchmarks

ℹ️ 16 benchmarks have no flamegraph

The flamegraph wasn't generated: most of the time was spent in runtime internals, which would leave nothing useful to analyze.

See affected benchmarks

⚡ 25 improved benchmarks
❌ 1 regressed benchmark
✅ 230 untouched benchmarks
⏩ 70 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
❌ CPU Simulation test_recursive_fibo_10 339.7 µs 692.7 µs -50.97%
⚡ CPU Simulation switch 1 586.1 µs 242.4 µs ×2.4
⚡ CPU Simulation switch 1 651.7 µs 313.9 µs ×2.1
⚡ CPU Simulation recursive fibo 10 677.4 µs 333.5 µs ×2
⚡ CPU Simulation test_recursive_fibo_10 678.1 µs 336.3 µs ×2
⚡ CPU Simulation recursive fibo 10 676.4 µs 342.2 µs +97.65%
⚡ Memory test_iterative_fibo_100 1,312 B 784 B +67.35%
⚡ Memory test_iterative_fibo_10 1,312 B 784 B +67.35%
⚡ Memory test_recursive_cached_fibo_20 1,312 B 784 B +67.35%
⚡ Memory test sync baz 10 1,312 B 784 B +67.35%
⚡ Memory test_recursive_cached_fibo_30 1,312 B 784 B +67.35%
⚡ Memory test_recursive_fibo_20 1,312 B 784 B +67.35%
⚡ Memory test_recursive_cached_fibo_10 1,312 B 784 B +67.35%
⚡ Memory test sync baz 100 1,312 B 784 B +67.35%
⚡ Walltime switch 2 120 ns 72 ns +66.67%
⚡ Walltime fibo darwin 9.1 ms 5.6 ms +61.55%
⚡ Walltime test_iterative_fibo_10 120 ns 84 ns +42.86%
⚡ Walltime test sync baz 10 120 ns 84 ns +42.86%
⚡ Walltime switch 2 96 ns 72 ns +33.33%
⚡ Memory wait 1sec 28.7 KB 22.7 KB +26.61%
... ... ... ... ... ...

ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing cod-3960-implement-process-isolation-in-codspeed-node (ede299e) with main (9c93085)

Open in CodSpeed

Footnotes

  1. 70 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@greptile-apps

greptile-apps Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[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 bench.table() working.

Fix All in Claude CodeFindings

  1. P1 Tinybench results stay empty ▶
Fix with agent prompt
### Issue 1
packages/tinybench-plugin/src/walltime.ts:104-106
The new `Forks` path saves pooled results only in CodSpeed's output. It never fills the parent `task.result`, yet `bench.run()` and `bench.runSync()` still return those parent tasks. Existing callers such as `examples/with-typescript-simple-esm/tinybench.ts` call `bench.table()` afterward and now have no completed results to display.

This also violates the repository guide's instruction to keep `bench.table()` working. Copy complete, compatible results back to the parent tasks before returning.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Walltime benchmarks now run in three fresh processes by default, and CodSpeed pools their samples.

  • Tinybench walltime benchmarks run in fresh processes and combine their samples.
  • Vitest 5 walltime benchmarks are queued and measured in fresh processes.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Discover benchmarks] --> B{Isolation enabled?}
  B -->|No| C[Measure in the current process]
  B -->|Yes| D[Start fresh processes for each benchmark]
  D --> E[Each process writes its samples]
  E --> F[Parent pools samples]
  F --> G[Write CodSpeed results]
  C --> G
Loading

Reviews (5) · Last reviewed commit: "fixup! feat(vitest): run walltime benchm..." · Reviewed by Greptile

@not-matthias
not-matthias force-pushed the cod-3960-implement-process-isolation-in-codspeed-node branch from 860d65c to 8038822 Compare October 29, 2026 11:10
@not-matthias not-matthias changed the title feat: run each benchmark in its own process feat: run walltime benchmarks in forks Oct 29, 2026
@not-matthias
not-matthias force-pushed the cod-3960-implement-process-isolation-in-codspeed-node branch from 8038822 to 86bdbbb Compare October 29, 2026 11:39
@not-matthias
not-matthias force-pushed the cod-3960-implement-process-isolation-in-codspeed-node branch from 86bdbbb to e9af7a1 Compare October 29, 2026 11:54

@GuillaumeLagrange GuillaumeLagrange left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

olgtm

Comment thread packages/core/src/isolation.ts Resolved
Comment thread packages/core/src/isolation.ts Resolved
Comment thread packages/core/src/walltime/utils.ts Resolved
Comment thread packages/core/src/walltime/utils.ts Resolved
Comment thread packages/tinybench-plugin/src/shared.ts Resolved
Comment thread packages/vitest-plugin/src/index.ts Resolved
Comment thread packages/vitest-plugin/src/index.ts Resolved
Comment thread packages/vitest-plugin/src/index.ts Resolved

@adriencaccia adriencaccia left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/core/src/isolation.ts Resolved
Comment thread packages/core/src/isolation.ts Resolved
Comment thread packages/core/src/isolation.ts Resolved
Comment thread packages/core/src/isolation.ts Resolved
Comment thread packages/core/src/isolation.ts Resolved
@not-matthias
not-matthias force-pushed the cod-3960-implement-process-isolation-in-codspeed-node branch from c376bfd to 3dca851 Compare November 3, 2026 15:46
@not-matthias
not-matthias added this pull request to stack #118 November 4, 2026 10:12
Comment thread packages/core/src/isolation.ts Resolved
@not-matthias
not-matthias force-pushed the cod-3960-implement-process-isolation-in-codspeed-node branch from f4d1b47 to f086cdf Compare November 4, 2026 12:04

@adriencaccia adriencaccia left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/vitest-plugin/src/index.ts Resolved
@not-matthias
not-matthias force-pushed the cod-3960-implement-process-isolation-in-codspeed-node branch from d2701ee to 5a0c4db Compare November 4, 2026 15:41
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
@not-matthias
not-matthias force-pushed the cod-3960-implement-process-isolation-in-codspeed-node branch from 5a0c4db to d172321 Compare November 4, 2026 16:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants