git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
111 lines
4.1 KiB
Rust
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");
|
|
}
|
|
}
|