Repository navigation
feat(divan): run each benchmark in its own process - #194
not-matthias wants to merge 8 commits into
Conversation
Merging this PR will degrade performance by 6.69%
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ❌ | Memory | fib_in_thread[32] |
424 B | 760 B | -44.21% |
| ❌ | Memory | fib_in_thread[30] |
424 B | 760 B | -44.21% |
| ❌ | Memory | fib_in_thread[31] |
424 B | 760 B | -44.21% |
| ❌ | Memory | fib_in_thread_bench_local[31] |
424 B | 760 B | -44.21% |
| ❌ | Memory | fib_in_thread_bench[32] |
424 B | 760 B | -44.21% |
| ❌ | Memory | fib_in_thread_bench_local[32] |
424 B | 760 B | -44.21% |
| ❌ | Memory | fib_in_thread_bench[30] |
424 B | 760 B | -44.21% |
| ❌ | Memory | fib_in_thread_bench[31] |
424 B | 760 B | -44.21% |
| ❌ | WallTime | mul |
2 ns | 3 ns | -33.33% |
| ❌ | Simulation | Iterative |
121.5 ns | 177.1 ns | -31.38% |
| ❌ | Simulation | Iterative[20] |
121.5 ns | 177.1 ns | -31.38% |
| ❌ | Simulation | Iterative[21] |
122.8 ns | 178.4 ns | -31.15% |
| ❌ | Simulation | generate_combinations[7] |
21.4 µs | 27.6 µs | -22.35% |
| ❌ | Simulation | recursive_memoized[HashMap<u64, u64>, 10] |
8.6 µs | 10.8 µs | -19.74% |
| ❌ | Simulation | add_two_integers[(42, 13)] |
233.8 ns | 288 ns | -18.81% |
| ❌ | Simulation | add_two_integers[(255, 255)] |
233.8 ns | 288 ns | -18.81% |
| ❌ | Simulation | add_two_integers[(1024, -1024)] |
295.2 ns | 349.4 ns | -15.5% |
| ❌ | Simulation | add_two_integers[(65535, -65535)] |
307 ns | 361.1 ns | -15% |
| ❌ | Simulation | generate_combinations[8] |
28.8 µs | 33.9 µs | -14.97% |
| ❌ | Simulation | generate_combinations[9] |
35.7 µs | 41.8 µs | -14.51% |
| ... | ... | ... | ... | ... | ... |
ℹ️ 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-3799-explore-process-per-benchmark-in-codspeed-rust (304fa86) with main (832f252)
c9919e1 to
3a215de
Compare
Congrats! CodSpeed is installed 🎉
You will start to see performance impacts in the reports once the benchmarks are run from your default branch.
|
3a215de to
fa0b668
Compare
|
The codspeed crate declared libc since its first commit but never used it.
Benchmarks of one bench binary share a process, so the allocator state one benchmark leaves behind (raised mmap threshold, fragmented bins, retained caches) changes the measurement of every benchmark after it. Adding or changing one benchmark can then shift unrelated results. Add `isolation::Isolation` for the harnesses. It is on by default and turned off with `CODSPEED_PROCESS_ISOLATION=false`. `Isolation::run` re-executes the bench binary with the same arguments and `CODSPEED_ISOLATED_BENCHMARK` set to one benchmark URI, so each benchmark gets a fresh process with its own heap and threads. The child only runs that benchmark (`only_benchmark`, which overrides the CLI filters), and a failing child's exit code is propagated (128 + N for signal N). Re-exec instead of fork(): fork only copies the calling thread, so threads the bench binary started before the harness (e.g. a Tokio runtime in `main`) would be missing in the child and work sent to them would hang.
Route every benchmark of the analysis harness through `Isolation::run`. A child process only matches its own benchmark URI and doesn't repeat the "Skipped" lines the parent already printed.
All criterion compat benchmarks go through `BenchmarkGroup::run_bench`; wrap the measurement in `Isolation::run` like divan. A child process replaces the CLI filter with an exact match on its benchmark URI.
4a49dfa to
9e80c3b
Compare
Build and run a divan fixture in simulation mode and check that: - every benchmark runs in a fresh process by default, including one that uses a multi-threaded Tokio runtime started in `main`; - `CODSPEED_PROCESS_ISOLATION=false` runs them all in one process; - a panicking benchmark fails the run with the panic exit code.
Three identical benchmarks that allocate a zeroed 4 MiB buffer. In one process the first one gets fresh mmap pages from glibc; freeing them raises the mmap threshold, so the next ones get heap memory that calloc must clear. Under callgrind they report 642, 127,593 and 229,862 instructions without isolation, and 642 each with it. `spawn_on_parent_runtime` awaits a task on a Tokio runtime started in `main`, which only works when each isolated process starts its own runtime.
Every benchmark now runs in its own process. A harness regression that leaves a child stuck must fail the job instead of blocking it until the GitHub Actions default of 6 hours.
9e80c3b to
304fa86
Compare
TLDR: Benchmarks in one bench binary share a process, so one benchmark can change the allocator state for every benchmark after it. With
CODSPEED_PROCESS_ISOLATION=1,divan_compatnow re-executes the binary once per benchmark (simulation mode only).current_exe()with__CODSPEED_DIVAN_BENCH_ID=<entry>[:<arg>].cargo-codspeedor the runner: they rely on the existing--trace-children=yes.allocator_ripplebench: three identicalcalloc(4 MiB)benchmarks. The first one frees an mmapped chunk, which raises glibc's mmap threshold, so the later ones get heap memory that must be cleared withmemset.Measured (
allocator_ripple, callgrind Ir, same on reruns):zeroed_buffer[1]zeroed_buffer[2]zeroed_buffer[3]Variance:
Review notes
divan_fork), memory-mode tracking of the child processes, criterion and bencher.Refs COD-3799