Files
wifi-densepose/crates/ruvector-mincut/benches/mincut_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

548 lines
18 KiB
Rust

//! Benchmarks for Dynamic Minimum Cut Algorithm
//!
//! Measures:
//! - Insert/delete throughput at various graph sizes
//! - Query latency
//! - Scaling behavior (subpolynomial verification)
//! - Comparison with static algorithms
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use rand::prelude::*;
use ruvector_mincut::graph::DynamicGraph;
use std::collections::HashSet;
/// Generate a random graph with n vertices and m edges
fn generate_random_graph(n: usize, m: usize, seed: u64) -> Vec<(u64, u64, f64)> {
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
let mut edges = Vec::with_capacity(m);
let mut edge_set = HashSet::new();
while edges.len() < m {
let u = rng.gen_range(0..n as u64);
let v = rng.gen_range(0..n as u64);
if u != v {
let key = if u < v { (u, v) } else { (v, u) };
if edge_set.insert(key) {
edges.push((u, v, 1.0));
}
}
}
edges
}
/// Generate a grid graph (good test case with known min cuts)
fn generate_grid_graph(width: usize, height: usize) -> Vec<(u64, u64, f64)> {
let mut edges = Vec::new();
for i in 0..height {
for j in 0..width {
let v = (i * width + j) as u64;
if j + 1 < width {
edges.push((v, v + 1, 1.0));
}
if i + 1 < height {
edges.push((v, v + width as u64, 1.0));
}
}
}
edges
}
/// Generate a complete graph with n vertices
fn generate_complete_graph(n: usize) -> Vec<(u64, u64, f64)> {
let mut edges = Vec::new();
for i in 0..n as u64 {
for j in (i + 1)..n as u64 {
edges.push((i, j, 1.0));
}
}
edges
}
/// Generate a sparse graph (average degree ~4)
fn generate_sparse_graph(n: usize, seed: u64) -> Vec<(u64, u64, f64)> {
let m = n * 2; // Average degree of 4
generate_random_graph(n, m, seed)
}
/// Generate a dense graph (edge probability ~0.3)
fn generate_dense_graph(n: usize, seed: u64) -> Vec<(u64, u64, f64)> {
let m = (n * (n - 1)) / 6; // ~30% of possible edges
generate_random_graph(n, m, seed)
}
/// Benchmark edge insertion throughput
fn bench_insert_edge(c: &mut Criterion) {
let mut group = c.benchmark_group("insert_edge");
for size in [100, 500, 1000, 5000, 10000].iter() {
let edges = generate_random_graph(*size, size * 2, 42);
group.throughput(Throughput::Elements(1));
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| {
b.iter_batched(
|| {
let graph = DynamicGraph::with_capacity(size, size * 3);
// Pre-populate with initial edges
for (u, v, w) in &edges {
let _ = graph.insert_edge(*u, *v, *w);
}
(graph, rand::rngs::StdRng::seed_from_u64(123))
},
|(graph, mut rng)| {
let u = rng.gen_range(0..size as u64);
let v = rng.gen_range(0..size as u64);
if u != v && !graph.has_edge(u, v) {
let _ = black_box(graph.insert_edge(u, v, 1.0));
}
},
criterion::BatchSize::SmallInput,
);
});
}
group.finish();
}
/// Benchmark edge deletion throughput
fn bench_delete_edge(c: &mut Criterion) {
let mut group = c.benchmark_group("delete_edge");
for size in [100, 500, 1000, 5000, 10000].iter() {
let edges = generate_random_graph(*size, size * 2, 42);
group.throughput(Throughput::Elements(1));
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, _| {
b.iter_batched(
|| {
let graph = DynamicGraph::with_capacity(*size, size * 3);
for (u, v, w) in &edges {
let _ = graph.insert_edge(*u, *v, *w);
}
graph
},
|graph| {
let edges_list = graph.edges();
if !edges_list.is_empty() {
let idx = rand::thread_rng().gen_range(0..edges_list.len());
let edge = edges_list[idx];
let _ = black_box(graph.delete_edge(edge.source, edge.target));
}
},
criterion::BatchSize::SmallInput,
);
});
}
group.finish();
}
/// Benchmark query operations (connectivity checks)
fn bench_query(c: &mut Criterion) {
let mut group = c.benchmark_group("query_connectivity");
for size in [100, 500, 1000, 5000, 10000].iter() {
let edges = generate_random_graph(*size, size * 2, 42);
group.throughput(Throughput::Elements(1));
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, _| {
let graph = DynamicGraph::with_capacity(*size, size * 3);
for (u, v, w) in &edges {
let _ = graph.insert_edge(*u, *v, *w);
}
b.iter(|| black_box(graph.is_connected()));
});
}
group.finish();
}
/// Benchmark degree queries
fn bench_degree_query(c: &mut Criterion) {
let mut group = c.benchmark_group("query_degree");
for size in [100, 500, 1000, 5000, 10000].iter() {
let edges = generate_random_graph(*size, size * 2, 42);
group.throughput(Throughput::Elements(1));
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| {
let graph = DynamicGraph::with_capacity(size, size * 3);
for (u, v, w) in &edges {
let _ = graph.insert_edge(*u, *v, *w);
}
let mut rng = rand::rngs::StdRng::seed_from_u64(456);
b.iter(|| {
let v = rng.gen_range(0..size as u64);
black_box(graph.degree(v))
});
});
}
group.finish();
}
/// Benchmark edge existence queries
fn bench_has_edge_query(c: &mut Criterion) {
let mut group = c.benchmark_group("query_has_edge");
for size in [100, 500, 1000, 5000, 10000].iter() {
let edges = generate_random_graph(*size, size * 2, 42);
group.throughput(Throughput::Elements(1));
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| {
let graph = DynamicGraph::with_capacity(size, size * 3);
for (u, v, w) in &edges {
let _ = graph.insert_edge(*u, *v, *w);
}
let mut rng = rand::rngs::StdRng::seed_from_u64(789);
b.iter(|| {
let u = rng.gen_range(0..size as u64);
let v = rng.gen_range(0..size as u64);
black_box(graph.has_edge(u, v))
});
});
}
group.finish();
}
/// Benchmark mixed workload: 50% inserts, 30% deletes, 20% queries
fn bench_mixed_workload(c: &mut Criterion) {
let mut group = c.benchmark_group("mixed_workload");
for size in [100, 500, 1000, 5000].iter() {
let initial_edges = generate_random_graph(*size, size * 2, 42);
group.throughput(Throughput::Elements(1));
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| {
b.iter_batched(
|| {
let graph = DynamicGraph::with_capacity(size, size * 3);
for (u, v, w) in &initial_edges {
let _ = graph.insert_edge(*u, *v, *w);
}
(graph, rand::rngs::StdRng::seed_from_u64(999))
},
|(graph, mut rng)| {
let op = rng.gen_range(0..10);
match op {
// 50% inserts (0-4)
0..=4 => {
let u = rng.gen_range(0..size as u64);
let v = rng.gen_range(0..size as u64);
if u != v && !graph.has_edge(u, v) {
let _ = graph.insert_edge(u, v, 1.0);
}
}
// 30% deletes (5-7)
5..=7 => {
let edges_list = graph.edges();
if !edges_list.is_empty() {
let idx = rng.gen_range(0..edges_list.len());
let edge = edges_list[idx];
let _ = graph.delete_edge(edge.source, edge.target);
}
}
// 20% queries (8-9)
_ => {
let u = rng.gen_range(0..size as u64);
let _ = black_box(graph.degree(u));
}
}
},
criterion::BatchSize::SmallInput,
);
});
}
group.finish();
}
/// Benchmark scaling behavior - verify subpolynomial update time
fn bench_scaling(c: &mut Criterion) {
let mut group = c.benchmark_group("scaling_analysis");
group.sample_size(20); // Reduce sample size for larger graphs
// Test sizes chosen to show subpolynomial scaling: n^(2/3)
let sizes = vec![100, 316, 1000, 3162, 10000];
for size in sizes {
let edges = generate_random_graph(size, size * 2, 42);
group.throughput(Throughput::Elements(1));
group.bench_with_input(
BenchmarkId::new("insert_scaling", size),
&size,
|b, &size| {
b.iter_batched(
|| {
let graph = DynamicGraph::with_capacity(size, size * 3);
for (u, v, w) in &edges {
let _ = graph.insert_edge(*u, *v, *w);
}
(graph, rand::rngs::StdRng::seed_from_u64(111))
},
|(graph, mut rng)| {
let u = rng.gen_range(0..size as u64);
let v = rng.gen_range(0..size as u64);
if u != v && !graph.has_edge(u, v) {
let _ = black_box(graph.insert_edge(u, v, 1.0));
}
},
criterion::BatchSize::SmallInput,
);
},
);
group.bench_with_input(BenchmarkId::new("delete_scaling", size), &size, |b, _| {
b.iter_batched(
|| {
let graph = DynamicGraph::with_capacity(size, size * 3);
for (u, v, w) in &edges {
let _ = graph.insert_edge(*u, *v, *w);
}
graph
},
|graph| {
let edges_list = graph.edges();
if !edges_list.is_empty() {
let idx = rand::thread_rng().gen_range(0..edges_list.len());
let edge = edges_list[idx];
let _ = black_box(graph.delete_edge(edge.source, edge.target));
}
},
criterion::BatchSize::SmallInput,
);
});
}
group.finish();
}
/// Benchmark different graph types
fn bench_graph_types(c: &mut Criterion) {
let mut group = c.benchmark_group("graph_types");
let size = 1000;
// Random graph
let random_edges = generate_random_graph(size, size * 2, 42);
group.bench_function("random_insert", |b| {
b.iter_batched(
|| DynamicGraph::with_capacity(size, size * 3),
|graph| {
for (u, v, w) in &random_edges {
let _ = black_box(graph.insert_edge(*u, *v, *w));
}
},
criterion::BatchSize::SmallInput,
);
});
// Grid graph (31x32 ≈ 1000 vertices)
let grid_edges = generate_grid_graph(31, 32);
group.bench_function("grid_insert", |b| {
b.iter_batched(
|| DynamicGraph::with_capacity(size, size * 2),
|graph| {
for (u, v, w) in &grid_edges {
let _ = black_box(graph.insert_edge(*u, *v, *w));
}
},
criterion::BatchSize::SmallInput,
);
});
// Complete graph (45 vertices ≈ 990 edges)
let complete_edges = generate_complete_graph(45);
group.bench_function("complete_insert", |b| {
b.iter_batched(
|| DynamicGraph::with_capacity(45, 1000),
|graph| {
for (u, v, w) in &complete_edges {
let _ = black_box(graph.insert_edge(*u, *v, *w));
}
},
criterion::BatchSize::SmallInput,
);
});
// Sparse graph
let sparse_edges = generate_sparse_graph(size, 42);
group.bench_function("sparse_insert", |b| {
b.iter_batched(
|| DynamicGraph::with_capacity(size, size * 3),
|graph| {
for (u, v, w) in &sparse_edges {
let _ = black_box(graph.insert_edge(*u, *v, *w));
}
},
criterion::BatchSize::SmallInput,
);
});
// Dense graph
let dense_edges = generate_dense_graph(size, 42);
group.bench_function("dense_insert", |b| {
b.iter_batched(
|| DynamicGraph::with_capacity(size, dense_edges.len() + 100),
|graph| {
for (u, v, w) in &dense_edges {
let _ = black_box(graph.insert_edge(*u, *v, *w));
}
},
criterion::BatchSize::SmallInput,
);
});
group.finish();
}
/// Benchmark graph statistics computation
fn bench_stats(c: &mut Criterion) {
let mut group = c.benchmark_group("stats_computation");
for size in [100, 500, 1000, 5000].iter() {
let edges = generate_random_graph(*size, size * 2, 42);
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, _| {
let graph = DynamicGraph::with_capacity(*size, size * 3);
for (u, v, w) in &edges {
let _ = graph.insert_edge(*u, *v, *w);
}
b.iter(|| black_box(graph.stats()));
});
}
group.finish();
}
/// Benchmark connected components computation
fn bench_connected_components(c: &mut Criterion) {
let mut group = c.benchmark_group("connected_components");
for size in [100, 500, 1000, 5000].iter() {
// Create a graph with multiple components
let mut edges = Vec::new();
let component_size = size / 5; // 5 components
for comp in 0..5 {
let offset = comp * component_size;
for i in 0..component_size - 1 {
let u = (offset + i) as u64;
let v = (offset + i + 1) as u64;
edges.push((u, v, 1.0));
}
}
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, _| {
let graph = DynamicGraph::with_capacity(*size, edges.len());
for (u, v, w) in &edges {
let _ = graph.insert_edge(*u, *v, *w);
}
b.iter(|| black_box(graph.connected_components()));
});
}
group.finish();
}
/// Benchmark neighbor iteration
fn bench_neighbors(c: &mut Criterion) {
let mut group = c.benchmark_group("neighbors_iteration");
for degree in [10, 50, 100, 500, 1000].iter() {
// Create a star graph with one high-degree vertex
let mut edges = Vec::new();
for i in 1..=*degree {
edges.push((0, i as u64, 1.0));
}
group.bench_with_input(BenchmarkId::from_parameter(degree), degree, |b, _| {
let graph = DynamicGraph::with_capacity(*degree + 1, *degree);
for (u, v, w) in &edges {
let _ = graph.insert_edge(*u, *v, *w);
}
b.iter(|| black_box(graph.neighbors(0)));
});
}
group.finish();
}
/// Benchmark batch operations
fn bench_batch_operations(c: &mut Criterion) {
let mut group = c.benchmark_group("batch_operations");
for batch_size in [10, 50, 100, 500, 1000].iter() {
let edges = generate_random_graph(5000, *batch_size, 42);
group.throughput(Throughput::Elements(*batch_size as u64));
group.bench_with_input(
BenchmarkId::new("batch_insert", batch_size),
batch_size,
|b, _| {
b.iter_batched(
|| DynamicGraph::with_capacity(5000, *batch_size + 100),
|graph| {
for (u, v, w) in &edges {
let _ = black_box(graph.insert_edge(*u, *v, *w));
}
},
criterion::BatchSize::SmallInput,
);
},
);
}
group.finish();
}
/// Benchmark memory footprint (using num_edges as proxy)
fn bench_memory_efficiency(c: &mut Criterion) {
let mut group = c.benchmark_group("memory_efficiency");
for size in [1000, 5000, 10000].iter() {
let edges = generate_random_graph(*size, size * 2, 42);
group.bench_with_input(BenchmarkId::new("graph_creation", size), size, |b, _| {
b.iter(|| {
let graph = DynamicGraph::with_capacity(*size, size * 3);
for (u, v, w) in &edges {
let _ = graph.insert_edge(*u, *v, *w);
}
black_box(graph)
});
});
}
group.finish();
}
criterion_group!(
graph_ops,
bench_insert_edge,
bench_delete_edge,
bench_degree_query,
bench_has_edge_query,
);
criterion_group!(
queries,
bench_query,
bench_stats,
bench_connected_components,
bench_neighbors,
);
criterion_group!(workloads, bench_mixed_workload, bench_batch_operations,);
criterion_group!(
scaling,
bench_scaling,
bench_graph_types,
bench_memory_efficiency,
);
criterion_main!(graph_ops, queries, workloads, scaling);