From 8671848e8829214b7e6efb859b45f8eeb4bf3bea Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Sat, 1 Aug 2026 13:06:55 +0000 Subject: [PATCH 1/3] chore: stabilize parallel eviction benchmark --- benches/eviction_walltime.rs | 78 +++++++++++++----------------------- 1 file changed, 28 insertions(+), 50 deletions(-) diff --git a/benches/eviction_walltime.rs b/benches/eviction_walltime.rs index cdf74c692..08560a9e1 100644 --- a/benches/eviction_walltime.rs +++ b/benches/eviction_walltime.rs @@ -9,12 +9,13 @@ //! cheap the workload could be without eviction bookkeeping or capacity-driven //! recomputation. //! -//! Database-owning benchmarks use single-iteration samples to avoid batching -//! multiple databases, and 20 samples to keep their setup cost bounded. +//! Revision-changing benchmarks use single-iteration samples to avoid batching +//! multiple databases, and 20 samples to keep their setup cost bounded. The +//! concurrent benchmark uses four synchronized workers. use std::hint::black_box; -use rayon::prelude::*; +use crossbeam_queue::SegQueue; use salsa::Database as _; #[path = "eviction/support.rs"] @@ -25,12 +26,10 @@ use support::{ prewarm, }; -fn main() { - rayon::ThreadPoolBuilder::new() - .num_threads(4) - .build_global() - .unwrap(); +const PARALLEL_WORKERS: usize = 4; +const PARALLEL_SAMPLES: usize = 20; +fn main() { divan::main(); } @@ -294,29 +293,35 @@ fn phase_change(bencher: divan::Bencher, policy: Policy) { /// Each worker repeatedly accesses a different prewarmed query, avoiding both /// misses and contention on the query itself. This isolates contention in the /// eviction policy's hit bookkeeping; `NoEviction` provides the baseline. +/// Divan synchronizes the workers before each timed sample, avoiding variance +/// from Rayon task dispatch and workers joining the workload at different times. #[divan::bench( args = [Policy::NoEviction, Policy::Lru], - sample_count = 20, + threads = PARALLEL_WORKERS, + sample_count = PARALLEL_SAMPLES as u32, sample_size = 1 )] fn parallel_fast_path(bencher: divan::Bencher, policy: Policy) { const ACCESSES_PER_WORKER: usize = 4_096; + let mut db = salsa::DatabaseImpl::new(); + policy.set_capacity(&mut db, PARALLEL_WORKERS); + let items = new_items(&db, PARALLEL_WORKERS); + prewarm(policy, &db, &items); + + let jobs = SegQueue::new(); + for item in items.iter().copied().cycle().take(PARALLEL_SAMPLES) { + jobs.push((db.clone(), item)); + } + bencher - .with_inputs(|| { - let worker_count = rayon::current_num_threads(); - let mut db = salsa::DatabaseImpl::new(); - policy.set_capacity(&mut db, worker_count); - let items = new_items(&db, worker_count); - prewarm(policy, &db, &items); - - items - .into_iter() - .map(|item| (db.clone(), item)) - .collect::>() - }) - .bench_local_refs(|jobs| { - black_box(parallel_access_repeated(policy, jobs, ACCESSES_PER_WORKER)) + .with_inputs(|| jobs.pop().expect("one prepared database handle per sample")) + .bench_refs(|(db, item)| { + black_box(access_all( + policy, + db, + std::iter::repeat_n(*item, ACCESSES_PER_WORKER), + )) }); } @@ -362,30 +367,3 @@ impl Workload { Value(sum) } } - -fn parallel_access_repeated( - policy: Policy, - jobs: &mut [(salsa::DatabaseImpl, Item)], - accesses_per_item: usize, -) -> Value { - match policy { - Policy::NoEviction => { - parallel_access_repeated_with(jobs, accesses_per_item, no_eviction_value) - } - Policy::Lru => parallel_access_repeated_with(jobs, accesses_per_item, lru_value), - } -} - -fn parallel_access_repeated_with( - jobs: &mut [(salsa::DatabaseImpl, Item)], - accesses_per_item: usize, - fetch: impl Fn(&dyn salsa::Database, Item) -> Value + Copy + Send + Sync, -) -> Value { - let sum = jobs - .par_iter_mut() - .map(|(db, item)| { - access_all_with(db, std::iter::repeat_n(*item, accesses_per_item), fetch).0 - }) - .reduce(|| 0, u64::wrapping_add); - Value(sum) -} From c655ea4f4430dc8dc9b6c98e413659eef63f2b88 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Sat, 1 Aug 2026 13:13:35 +0000 Subject: [PATCH 2/3] chore: simplify parallel eviction benchmark setup --- benches/eviction_walltime.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/benches/eviction_walltime.rs b/benches/eviction_walltime.rs index 08560a9e1..ca67791b4 100644 --- a/benches/eviction_walltime.rs +++ b/benches/eviction_walltime.rs @@ -14,8 +14,8 @@ //! concurrent benchmark uses four synchronized workers. use std::hint::black_box; +use std::sync::Mutex; -use crossbeam_queue::SegQueue; use salsa::Database as _; #[path = "eviction/support.rs"] @@ -27,7 +27,6 @@ use support::{ }; const PARALLEL_WORKERS: usize = 4; -const PARALLEL_SAMPLES: usize = 20; fn main() { divan::main(); @@ -298,7 +297,7 @@ fn phase_change(bencher: divan::Bencher, policy: Policy) { #[divan::bench( args = [Policy::NoEviction, Policy::Lru], threads = PARALLEL_WORKERS, - sample_count = PARALLEL_SAMPLES as u32, + sample_count = 20, sample_size = 1 )] fn parallel_fast_path(bencher: divan::Bencher, policy: Policy) { @@ -309,13 +308,14 @@ fn parallel_fast_path(bencher: divan::Bencher, policy: Policy) { let items = new_items(&db, PARALLEL_WORKERS); prewarm(policy, &db, &items); - let jobs = SegQueue::new(); - for item in items.iter().copied().cycle().take(PARALLEL_SAMPLES) { - jobs.push((db.clone(), item)); - } + let jobs = items + .into_iter() + .map(|item| (db.clone(), item)) + .collect::>(); + let jobs = Mutex::new(jobs.into_iter().cycle()); bencher - .with_inputs(|| jobs.pop().expect("one prepared database handle per sample")) + .with_inputs(|| jobs.lock().unwrap().next().unwrap()) .bench_refs(|(db, item)| { black_box(access_all( policy, From 49398e8d17e96ec1a144bcdeb560adadfca4c1ca Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Sat, 1 Aug 2026 13:15:39 +0000 Subject: [PATCH 3/3] chore: simplify parallel benchmark database handles --- benches/eviction_walltime.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/benches/eviction_walltime.rs b/benches/eviction_walltime.rs index ca67791b4..5fc0063b6 100644 --- a/benches/eviction_walltime.rs +++ b/benches/eviction_walltime.rs @@ -14,7 +14,10 @@ //! concurrent benchmark uses four synchronized workers. use std::hint::black_box; -use std::sync::Mutex; +use std::sync::{ + Mutex, + atomic::{AtomicUsize, Ordering}, +}; use salsa::Database as _; @@ -308,14 +311,14 @@ fn parallel_fast_path(bencher: divan::Bencher, policy: Policy) { let items = new_items(&db, PARALLEL_WORKERS); prewarm(policy, &db, &items); - let jobs = items - .into_iter() - .map(|item| (db.clone(), item)) - .collect::>(); - let jobs = Mutex::new(jobs.into_iter().cycle()); + let db = Mutex::new(db); + let next_worker = AtomicUsize::new(0); bencher - .with_inputs(|| jobs.lock().unwrap().next().unwrap()) + .with_inputs(|| { + let worker = next_worker.fetch_add(1, Ordering::Relaxed); + (db.lock().unwrap().clone(), items[worker % items.len()]) + }) .bench_refs(|(db, item)| { black_box(access_all( policy,