From cbde9bbda70f2887d10f1dfef42415142a3f5d2b Mon Sep 17 00:00:00 2001 From: not-matthias Date: Tue, 1 Sep 2026 21:14:05 +0200 Subject: [PATCH 1/3] test(divan): add native threaded benchmark fixture Exercise Divan native threads with four workers in the walltime benchmark suite. Keep the simulation compatibility path single-threaded because its benchmark options do not expose the native threads setting. --- crates/divan_compat/benches/thread_example.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/divan_compat/benches/thread_example.rs b/crates/divan_compat/benches/thread_example.rs index 1695033b..dc0fbe28 100644 --- a/crates/divan_compat/benches/thread_example.rs +++ b/crates/divan_compat/benches/thread_example.rs @@ -28,6 +28,15 @@ fn fib_in_thread_bench_local(bencher: codspeed_divan_compat::Bencher, n: usize) }) } +#[cfg_attr( + not(codspeed), + codspeed_divan_compat::bench(threads = 4, sample_count = 1) +)] +#[cfg_attr(codspeed, codspeed_divan_compat::bench(sample_count = 1))] +fn divan_threads() { + codspeed_divan_compat::black_box(fibo(20)); +} + fn main() { codspeed_divan_compat::main(); } From 6d5bac27eaa20a3b7c8210811a2ae2288e45f38d Mon Sep 17 00:00:00 2001 From: not-matthias Date: Fri, 4 Sep 2026 16:16:58 +0200 Subject: [PATCH 2/3] chore(deps): bump instrument-hooks to thread-safe C API The runner FIFO transactions and the environment maps are now serialized inside instrument-hooks itself, so callers can invoke the C API from several threads without an external lock. --- crates/codspeed/instrument-hooks | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/codspeed/instrument-hooks b/crates/codspeed/instrument-hooks index 3d131089..4c76dbb5 160000 --- a/crates/codspeed/instrument-hooks +++ b/crates/codspeed/instrument-hooks @@ -1 +1 @@ -Subproject commit 3d131089cd93dd2971d35776334c9b6142c43ec1 +Subproject commit 4c76dbb5b99fc4927289281c7b7ca71cc46e6836 From 5a9838fc8799f1f3ac702a74a422af26f73aef83 Mon Sep 17 00:00:00 2001 From: not-matthias Date: Fri, 4 Sep 2026 16:16:59 +0200 Subject: [PATCH 3/3] fix(divan): support threaded walltime benchmarks Aggregate worker timestamps into one benchmark marker envelope per Divan sampling round so benchmark bodies stay parallel without interleaving FIFO requests. --- .../divan_compat/divan_fork/src/bench/mod.rs | 44 ++++++++++++------- .../divan_fork/src/stats/sample.rs | 2 + 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/crates/divan_compat/divan_fork/src/bench/mod.rs b/crates/divan_compat/divan_fork/src/bench/mod.rs index 560fe5f8..5303ae25 100644 --- a/crates/divan_compat/divan_fork/src/bench/mod.rs +++ b/crates/divan_compat/divan_fork/src/bench/mod.rs @@ -698,10 +698,18 @@ impl<'a> BenchContext<'a> { }; // Sample loop: - let ([start, end], alloc_info) = + let ([start, end], alloc_info, [benchmark_start, benchmark_end]) = record_sample(sample_size as usize, barrier.as_ref(), &mut count_input); - RawSample { start, end, timer, alloc_info, counter_totals } + RawSample { + start, + end, + benchmark_start, + benchmark_end, + timer, + alloc_info, + counter_totals, + } }; // Sample loop: @@ -723,6 +731,11 @@ impl<'a> BenchContext<'a> { std::slice::from_raw_parts(raw_samples.as_ptr().cast(), raw_samples.len()) } }; + let benchmark_start = + raw_samples.iter().map(|sample| sample.benchmark_start).min().unwrap(); + let benchmark_end = + raw_samples.iter().map(|sample| sample.benchmark_end).max().unwrap(); + InstrumentHooks::instance().add_benchmark_timestamps(benchmark_start, benchmark_end); // If testing, exit the benchmarking loop immediately after timing a // single run. @@ -825,8 +838,11 @@ impl<'a> BenchContext<'a> { gen_input: impl Fn() -> I, benched: impl Fn(&UnsafeCell>) -> O, drop_input: impl Fn(&UnsafeCell>), - ) -> impl Fn(usize, Option<&Barrier>, &mut dyn FnMut(&I)) -> ([Timestamp; 2], ThreadAllocInfo) - { + ) -> impl Fn( + usize, + Option<&Barrier>, + &mut dyn FnMut(&I), + ) -> ([Timestamp; 2], ThreadAllocInfo, [u64; 2]) { // We defer: // - Usage of `gen_input` values. // - Drop destructor for `O`, preventing it from affecting sample @@ -897,8 +913,9 @@ impl<'a> BenchContext<'a> { // benchmarking. let sample_start: UntaggedTimestamp; let sample_end: UntaggedTimestamp; + let benchmark_start: u64; + let benchmark_end: u64; - let instrument_hooks = InstrumentHooks::instance(); if size_of::() == 0 && (size_of::() == 0 || !mem::needs_drop::()) { // Use a range instead of `defer_store` to make the benchmarking // loop cheaper. @@ -915,7 +932,7 @@ impl<'a> BenchContext<'a> { sync_threads(true); - let start_time = InstrumentHooks::current_timestamp(); + benchmark_start = InstrumentHooks::current_timestamp(); sample_start = UntaggedTimestamp::start(timer_kind); // Sample loop: @@ -928,8 +945,7 @@ impl<'a> BenchContext<'a> { } sample_end = UntaggedTimestamp::end(timer_kind); - let end_time = InstrumentHooks::current_timestamp(); - instrument_hooks.add_benchmark_timestamps(start_time, end_time); + benchmark_end = InstrumentHooks::current_timestamp(); sync_threads(false); save_alloc_info(); @@ -972,7 +988,7 @@ impl<'a> BenchContext<'a> { let defer_slots_iter = defer_slots_slice.iter(); sync_threads(true); - let start_time = InstrumentHooks::current_timestamp(); + benchmark_start = InstrumentHooks::current_timestamp(); sample_start = UntaggedTimestamp::start(timer_kind); // Sample loop: @@ -987,8 +1003,7 @@ impl<'a> BenchContext<'a> { } sample_end = UntaggedTimestamp::end(timer_kind); - let end_time = InstrumentHooks::current_timestamp(); - instrument_hooks.add_benchmark_timestamps(start_time, end_time); + benchmark_end = InstrumentHooks::current_timestamp(); sync_threads(false); save_alloc_info(); @@ -1028,7 +1043,7 @@ impl<'a> BenchContext<'a> { let defer_inputs_iter = defer_inputs_slice.iter(); sync_threads(true); - let start_time = InstrumentHooks::current_timestamp(); + benchmark_start = InstrumentHooks::current_timestamp(); sample_start = UntaggedTimestamp::start(timer_kind); // Sample loop: @@ -1039,8 +1054,7 @@ impl<'a> BenchContext<'a> { } sample_end = UntaggedTimestamp::end(timer_kind); - let end_time = InstrumentHooks::current_timestamp(); - instrument_hooks.add_benchmark_timestamps(start_time, end_time); + benchmark_end = InstrumentHooks::current_timestamp(); sync_threads(false); save_alloc_info(); @@ -1065,7 +1079,7 @@ impl<'a> BenchContext<'a> { [sample_start.into_timestamp(timer_kind), sample_end.into_timestamp(timer_kind)] }; - (interval, saved_alloc_info) + (interval, saved_alloc_info, [benchmark_start, benchmark_end]) } } diff --git a/crates/divan_compat/divan_fork/src/stats/sample.rs b/crates/divan_compat/divan_fork/src/stats/sample.rs index b1e1727d..f491a3c4 100644 --- a/crates/divan_compat/divan_fork/src/stats/sample.rs +++ b/crates/divan_compat/divan_fork/src/stats/sample.rs @@ -22,6 +22,8 @@ pub(crate) struct TimeSample { pub(crate) struct RawSample { pub start: Timestamp, pub end: Timestamp, + pub benchmark_start: u64, + pub benchmark_end: u64, pub timer: Timer, pub alloc_info: ThreadAllocInfo, pub counter_totals: [u128; KnownCounterKind::COUNT],