Skip to content

perf(tinybench): batch fast walltime invocations into rounds - #117

Draft
not-matthias wants to merge 11 commits into
cod-3960-implement-process-isolation-in-codspeed-nodefrom
cod-4104-batch-fast-tinybench-walltime-benchmarks-into-1-ms-rounds
Draft

not-matthias wants to merge 11 commits into
cod-3960-implement-process-isolation-in-codspeed-nodefrom
cod-4104-batch-fast-tinybench-walltime-benchmarks-into-1-ms-rounds

Conversation

@not-matthias

Copy link
Copy Markdown
Member

TLDR: Batch fast Tinybench walltime calls into approximately 1 ms rounds to reduce timer and sample-processing overhead (stacked on #101).

  • Estimate batch size from warmup, or calibrate after lifecycle setup when warmup is disabled. Later forks reuse the first fork's size.
  • Normalize CodSpeed latency statistics per invocation; keep total measured time unscaled. Samples describe batch averages, not individual-call tails.
  • Preserve sequential async calls. Per-invocation hooks and task concurrency stay unbatched; fallback, simulation, and Vitest measurement behavior are unchanged.

Local iterative Fibonacci(10), Node 24.22.1 / Tinybench 4.0.1, 1 s measurement + 100 ms warmup per process, one run per configuration:

Mode Elapsed before → after Samples before → after
Shared process 20.06 s → 1.11 s 28,006,582 → 891
Two forks 59.39 s → 10.33 s 58,596,190 → 1,760

Peak RSS of a measuring child in the two-fork run: 3,769 MiB → 71 MiB. Local smoke checks also cover Tinybench 6.2.2, lifecycle hooks, slow rounds, concurrency, fallback, and simulation.

Closes COD-4104.

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
Replace JSON sample transport with gzip-compressed V8 serialization to avoid constructing strings beyond the V8 string-size limit. Update both the child writer and parent reader, using fast gzip compression while preserving all samples.
Calibrate approximately 1 ms rounds from warmup and share the iteration count across isolated forks. Normalize latency statistics per invocation while retaining raw total time. Keep per-invocation hooks and concurrent tasks unbatched, and cover shared, forked, synchronous, and asynchronous execution.
@codspeed

codspeed Bot commented Nov 3, 2026

Copy link
Copy Markdown

Merging this PR will regress 12 benchmarks

5 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

⚠️ Unable to measure the execution of 8 benchmarks

These benchmarks measured no execution time, so their results are not comparable and count as unchanged. This usually happens when the compiler removes the code under test.

Prevent compiler optimizations

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

⚠️ 17 benchmarks with a significant change ran on different hardware

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

Learn more about Macro Runners

See affected benchmarks

⚠️ 38 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

⚡ 27 improved benchmarks
❌ 12 regressed benchmarks
✅ 225 untouched benchmarks
⏩ 62 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
❌ CPU Simulation recursive fibo 10 334 µs 679.5 µs -50.85%
❌ CPU Simulation test_recursive_fibo_10 345.9 µs 678.8 µs -49.05%
❌ Walltime fibo darwin 5.2 ms 8.7 ms -40.26%
❌ Memory test_iterative_fibo_100 784 B 1,312 B -40.24%
❌ Memory test_iterative_fibo_10 784 B 1,312 B -40.24%
❌ Memory test_recursive_cached_fibo_20 784 B 1,312 B -40.24%
❌ Memory test sync baz 10 784 B 1,312 B -40.24%
❌ Memory test_recursive_cached_fibo_30 784 B 1,312 B -40.24%
❌ Memory test_recursive_fibo_20 784 B 1,312 B -40.24%
❌ Memory test_recursive_cached_fibo_10 784 B 1,312 B -40.24%
❌ Memory test sync baz 100 784 B 1,312 B -40.24%
❌ Memory wait 1sec 22.1 KB 27.5 KB -19.91%
Walltime switch 2 84 ns < 1 ns ×150
Walltime switch 1 84 ns < 1 ns ×150
Walltime switch 1 72 ns < 1 ns ×130
Walltime switch 2 72 ns < 1 ns ×130
Walltime switch 1 72 ns < 1 ns ×130
Walltime switch 2 72 ns < 1 ns ×130
Walltime switch 2 72 ns < 1 ns ×130
Walltime switch 1 72 ns < 1 ns ×130
... ... ... ... ... ...

ℹ️ 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-4104-batch-fast-tinybench-walltime-benchmarks-into-1-ms-rounds (cfed9fe) with cod-3960-implement-process-isolation-in-codspeed-node (3dca851)

Open in CodSpeed

Footnotes

  1. 62 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. ↩

@not-matthias
not-matthias added this pull request to stack #118 November 4, 2026 10:12
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.

1 participant