aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-03 05:53:00 +0100
committertv <tv@krebsco.de>2026-11-03 05:53:00 +0100
commit96ccd0b83fba00c3d37e6f31ea15c31b745347d5 (patch)
treef7bb630e49aef6ea419557f42c669d0cda9c7565
parentc107b3305060d93c56010f785eb420d132071e0c (diff)
parallel propagation benchmark
-rw-r--r--docs/development.md4
-rw-r--r--src/simtest/bench.rs68
2 files changed, 57 insertions, 15 deletions
diff --git a/docs/development.md b/docs/development.md
index 70ba03f..0b424e3 100644
--- a/docs/development.md
+++ b/docs/development.md
@@ -22,7 +22,9 @@ messages, `document/bench.rs`), the remakes' replay (`remake/bench.rs`), and wha
passing changes on costs over the simulated network (`simtest/bench.rs`: messages and
bytes per write by kind, propagation delays, for a full mesh, a writer behind NAT, two
transports and partial meshes of 50 and 100 nodes; several minutes, or one scenario
-alone with `BENCH_SCENARIO`, part of its name: `BENCH_SCENARIO=NAT`), and
+alone with `BENCH_SCENARIO`, part of its name: `BENCH_SCENARIO=NAT`; all at once with
+`BENCH_PARALLEL=1`, faster on many cores, though sharing them makes delays come out
+somewhat higher), and
the same estimated on the member list's graph alone for up to 2000 members, with some
links down, as now and along the writer's tree (`swarm/topology/tests.rs`,
`bench_writer_trees`). One of
diff --git a/src/simtest/bench.rs b/src/simtest/bench.rs
index babd8e9..d4a254f 100644
--- a/src/simtest/bench.rs
+++ b/src/simtest/bench.rs
@@ -12,6 +12,7 @@
//! long: not taken off, since writes make keepalives unneeded (a pair that exchanges
//! anything needs none), so the quiet phase's count isn't part of a workload's.
+use std::fmt::Write;
use std::time::Duration;
use serde_json::json;
@@ -35,6 +36,10 @@ const KEEPALIVE: Duration = Duration::from_secs(5);
const QUIET: Duration = Duration::from_secs(20);
/// Only the scenarios whose names contain this, if set: `BENCH_SCENARIO=NAT`.
const ONLY: &str = "BENCH_SCENARIO";
+/// If set, all scenarios at once (`BENCH_PARALLEL=1`): several times faster on many
+/// cores, but they share them, so delays (and what depends on timing) may come out
+/// higher than alone; message counts barely.
+const PARALLEL: &str = "BENCH_PARALLEL";
const SETTLE: Duration = Duration::from_secs(180);
/// How the swarm is made.
@@ -134,15 +139,28 @@ const SCENARIOS: &[Scenario] = &[
},
];
-#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
+#[tokio::test(flavor = "multi_thread", worker_threads = 16)]
#[ignore = "a benchmark: cargo benchmarks"]
async fn bench_propagation() {
// Past the test harness's `test … ...`, so each report is on a line of its own.
println!();
let only = std::env::var(ONLY).unwrap_or_default();
- for (seed, scenario) in (70..).zip(SCENARIOS) {
- if scenario.name.contains(&only) {
- run(seed, scenario).await;
+ let chosen: Vec<(u64, &'static Scenario)> = (70..)
+ .zip(SCENARIOS)
+ .filter(|(_, scenario)| scenario.name.contains(&only))
+ .collect();
+ if std::env::var_os(PARALLEL).is_some() {
+ // All at once, each reported when all are done, in order.
+ let running: Vec<_> = chosen
+ .into_iter()
+ .map(|(seed, scenario)| tokio::spawn(run(seed, scenario)))
+ .collect();
+ for report in running {
+ print!("{}", report.await.expect("scenario panicked"));
+ }
+ } else {
+ for (seed, scenario) in chosen {
+ print!("{}", run(seed, scenario).await);
}
}
}
@@ -236,7 +254,9 @@ fn counters_mut(by: &mut ByKind) -> [&mut Counter; 9] {
]
}
-async fn run(seed: u64, scenario: &Scenario) {
+/// Runs `scenario`; returns its report.
+async fn run(seed: u64, scenario: &'static Scenario) -> String {
+ let mut out = String::new();
let sim = Sim::with_identity(seed);
let ids: Vec<String> = (0..scenario.nodes).map(|i| format!("n{i:03}")).collect();
(scenario.net)(&sim, &ids);
@@ -290,7 +310,8 @@ async fn run(seed: u64, scenario: &Scenario) {
let n = halves.len();
let quiet = (halves[n.saturating_sub(3)], halves[n - 1]);
- println!(
+ let _ = writeln!(
+ out,
"\n{} ({} writes at {} a second each)",
scenario.name,
WRITES,
@@ -314,21 +335,36 @@ async fn run(seed: u64, scenario: &Scenario) {
let written = Instant::now();
sim.wait_converged(all, SETTLE).await;
let converged = written.elapsed();
- report(workload, quiet, (start, Totals::of(&nodes)), converged);
+ report(
+ &mut out,
+ workload,
+ quiet,
+ (start, Totals::of(&nodes)),
+ converged,
+ );
}
+ out
}
/// The workload's counts (`during`), and the quiet phase's for as long.
-fn report(workload: &str, quiet: (Totals, Totals), during: (Totals, Totals), converged: Duration) {
+fn report(
+ out: &mut String,
+ workload: &str,
+ quiet: (Totals, Totals),
+ during: (Totals, Totals),
+ converged: Duration,
+) {
let took = during.1.at - during.0.at;
let scale = took.as_secs_f64() / (quiet.1.at - quiet.0.at).as_secs_f64();
let writes = WRITES as f64;
- println!(
+ let _ = writeln!(
+ out,
" {workload}: {:.1} s, converged {} ms after the last write",
took.as_secs_f64(),
converged.as_millis()
);
- println!(
+ let _ = writeln!(
+ out,
" {:<13} {:>9} {:>9} {:>11} (quiet phase, as long: msgs)",
"kind", "msgs", "/write", "bytes/write"
);
@@ -351,20 +387,23 @@ fn report(workload: &str, quiet: (Totals, Totals), during: (Totals, Totals), con
let bytes = (d1.bytes - d0.bytes) as f64;
all_messages += messages;
all_bytes += bytes;
- println!(
+ let _ = writeln!(
+ out,
" {kind:<13} {messages:>9.0} {:>9.1} {:>11.0} ({quiet_messages:.0})",
messages / writes,
bytes / writes
);
}
- println!(
+ let _ = writeln!(
+ out,
" {:<13} {all_messages:>9.0} {:>9.1} {:>11.0}",
"all",
all_messages / writes,
all_bytes / writes
);
let (r0, r1) = (during.0.relay, during.1.relay);
- println!(
+ let _ = writeln!(
+ out,
" relay per write: {:.1} announcement rounds, {:.1} pulls ({:.1} queued), {:.1} along writers' trees",
(r1.announce_rounds - r0.announce_rounds) as f64 / writes,
(r1.pulls - r0.pulls) as f64 / writes,
@@ -387,7 +426,8 @@ fn report(workload: &str, quiet: (Totals, Totals), during: (Totals, Totals), con
super::soak::percentile_bound(&propagation, quantile)
.map_or("more than an hour".to_owned(), |ms| format!("{ms} ms"))
};
- println!(
+ let _ = writeln!(
+ out,
" propagation: {} arrivals, mean {} ms, half within {}, 99% within {}",
propagation.count,
propagation.sum_ms / propagation.count.max(1),