Files
wifi-densepose/crates/ruvector-mincut/tests/canonical_bench.rs
ruv d803bfe2b1 Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
2026-02-28 14:39:40 -05:00

111 lines
4.1 KiB
Rust

//! Performance benchmark for canonical min-cut.
//! Run with: cargo test -p ruvector-mincut --features canonical --test canonical_bench --release -- --nocapture
#[cfg(feature = "canonical")]
mod bench {
use ruvector_mincut::canonical::CactusGraph;
use ruvector_mincut::graph::DynamicGraph;
use std::time::Instant;
/// Benchmark at 30 vertices (typical subgraph partition size).
/// The CactusGraph uses Stoer-Wagner (O(n^3)), so performance scales
/// cubically. For WASM tiles (<=256 vertices), the ArenaCactus path
/// is used instead (measured at ~3µs in the gate-kernel benchmark).
#[test]
fn bench_canonical_mincut_30v() {
let mut graph = DynamicGraph::new();
for i in 0..30u64 {
graph.add_vertex(i);
}
// Ring + cross edges (~90 edges)
for i in 0..30u64 {
let _ = graph.insert_edge(i, (i + 1) % 30, 1.0);
}
for i in 0..30u64 {
let _ = graph.insert_edge(i, (i + 11) % 30, 0.5);
let _ = graph.insert_edge(i, (i + 19) % 30, 0.3);
}
// Warm up
let _ = CactusGraph::build_from_graph(&graph);
// Benchmark cactus construction
let n_iter = 100;
let start = Instant::now();
for _ in 0..n_iter {
let cactus = CactusGraph::build_from_graph(&graph);
std::hint::black_box(&cactus);
}
let avg_cactus_us = start.elapsed().as_micros() as f64 / n_iter as f64;
// Benchmark canonical cut extraction
let cactus = CactusGraph::build_from_graph(&graph);
let start = Instant::now();
for _ in 0..n_iter {
let result = cactus.canonical_cut();
std::hint::black_box(&result);
}
let avg_cut_us = start.elapsed().as_micros() as f64 / n_iter as f64;
// Determinism: all 100 produce identical result
let reference = cactus.canonical_cut();
for _ in 0..100 {
let result = cactus.canonical_cut();
assert_eq!(result.value, reference.value);
assert_eq!(result.canonical_key, reference.canonical_key);
}
let total = avg_cactus_us + avg_cut_us;
println!("\n=== Canonical Min-Cut (30v, ~90e) ===");
println!(" CactusGraph build: {:.1} µs", avg_cactus_us);
println!(" Canonical cut: {:.1} µs", avg_cut_us);
println!(
" Total: {:.1} µs (target: < 3000 µs native)",
total
);
println!(" Cut value: {}", reference.value);
println!(" NOTE: WASM ArenaCactus (64v) = ~3µs (see gate-kernel bench)");
// Native CactusGraph uses heap-allocated Stoer-Wagner (O(n^3));
// the WASM ArenaCactus path (stack-allocated) is 500x faster.
assert!(
total < 3000.0,
"Exceeded 3ms native target: {:.1} µs",
total
);
}
/// Also benchmark at 100 vertices to track scalability (informational, no assertion).
#[test]
fn bench_canonical_mincut_100v_info() {
let mut graph = DynamicGraph::new();
for i in 0..100u64 {
graph.add_vertex(i);
}
for i in 0..100u64 {
let _ = graph.insert_edge(i, (i + 1) % 100, 1.0);
}
for i in 0..100u64 {
let _ = graph.insert_edge(i, (i + 37) % 100, 0.5);
let _ = graph.insert_edge(i, (i + 73) % 100, 0.3);
}
let _ = CactusGraph::build_from_graph(&graph);
let n_iter = 20;
let start = Instant::now();
for _ in 0..n_iter {
let cactus = CactusGraph::build_from_graph(&graph);
let _ = cactus.canonical_cut();
std::hint::black_box(&cactus);
}
let avg_total_us = start.elapsed().as_micros() as f64 / n_iter as f64;
println!("\n=== Canonical Min-Cut Scalability (100v, ~300e) ===");
println!(
" Total (build+cut): {:.1} µs (informational)",
avg_total_us
);
println!(" Stoer-Wagner is O(n^3), scales cubically with graph size");
}
}