From 6339ebadba9b1d462afd5917dd9014017f14bf97 Mon Sep 17 00:00:00 2001 From: wan9chi Date: Sun, 16 Aug 2026 23:11:17 +0800 Subject: [PATCH] test(fspy-benchmark): add a contended access row MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The thread count was one constant shared by every suite, fixed at two — enough to represent a normal tracked process, but not enough to make writers fight over the channel's counters. Each record costs two atomic read-modify-writes on words every other thread is touching, and two threads barely provoke that. The count moves onto the suite, so the existing rows keep their two threads and their comparability, and a new `access-contended` row runs the same opens under eight. It halves the iterations over half the opens, so four times the threads cost about the same wall clock. Co-Authored-By: Claude Fable 5 --- crates/fspy_benchmark/src/main.rs | 32 +++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/crates/fspy_benchmark/src/main.rs b/crates/fspy_benchmark/src/main.rs index 17f69e6a4..51b2c574c 100644 --- a/crates/fspy_benchmark/src/main.rs +++ b/crates/fspy_benchmark/src/main.rs @@ -29,10 +29,6 @@ const DYNAMIC_TARGET: &str = env!("CARGO_BIN_FILE_FSPY_BENCHMARK_TARGET"); #[cfg(all(target_os = "linux", target_arch = "x86_64"))] const STATIC_TARGET: &str = env!("CARGO_BIN_FILE_FSPY_BENCHMARK_STATIC_TARGET"); -/// Threads the target runs, passed through to it. Two, because a process that -/// fspy tracks rarely accesses files from one thread. -const THREADS: &str = "2"; - /// What a suite reads out of its launches. #[derive(Clone, Copy)] enum Metric { @@ -44,6 +40,10 @@ enum Metric { struct Suite { name: &'static str, + /// Threads the target runs, passed through to it. Two for most suites, + /// because a process that fspy tracks rarely accesses files from one + /// thread. + threads: &'static str, /// Opens per target thread, passed through to it. opens: &'static str, /// Measured iterations. Each one launches every arm once. @@ -62,6 +62,7 @@ struct Suite { /// affords fewer of them in the same time. const LAUNCH_SUITE: Suite = Suite { name: "launch", + threads: "2", opens: "0", iterations: if cfg!(windows) { 150 } else { 300 }, warmup: 5, @@ -75,6 +76,7 @@ const LAUNCH_SUITE: Suite = Suite { /// joining on top. const ACCESS_SUITE: Suite = Suite { name: "access", + threads: "2", opens: "2048", iterations: 102, warmup: 3, @@ -84,6 +86,7 @@ const ACCESS_SUITE: Suite = Suite { const RELATIVE_ACCESS_SUITE: Suite = Suite { name: "access-relative", + threads: "2", opens: "2048", iterations: 102, warmup: 3, @@ -91,6 +94,22 @@ const RELATIVE_ACCESS_SUITE: Suite = Suite { relative: true, }; +/// The same opens under enough threads to make them fight over the tracker's +/// shared counters. Each record costs two atomic read-modify-writes on words +/// every other thread is also touching, so this is the row that prices how +/// that contention scales; the two-thread rows barely provoke it. Runs half +/// the iterations of the plain suite over half the opens, so four times the +/// threads cost about the same wall clock. +const CONTENDED_ACCESS_SUITE: Suite = Suite { + name: "access-contended", + threads: "8", + opens: "1024", + iterations: 54, + warmup: 3, + metric: Metric::Typical, + relative: false, +}; + struct Backend { name: &'static str, target: &'static str, @@ -114,7 +133,8 @@ fn main() { validate(base_launcher, backend.target, relative); } } - for suite in [&LAUNCH_SUITE, &ACCESS_SUITE, &RELATIVE_ACCESS_SUITE] { + for suite in [&LAUNCH_SUITE, &ACCESS_SUITE, &RELATIVE_ACCESS_SUITE, &CONTENDED_ACCESS_SUITE] + { run_suite(backend, suite, base_launcher.as_deref()); } } @@ -237,7 +257,7 @@ fn launch(launcher: &OsStr, mode: Option<&str>, backend: &Backend, suite: &Suite command.arg("--relative"); } let output = command - .args([backend.target, THREADS, suite.opens]) + .args([backend.target, suite.threads, suite.opens]) .stdin(Stdio::null()) .stderr(Stdio::inherit()) .output()