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

497 lines
16 KiB
Rust

//! Benchmarks for bounded-range dynamic minimum cut
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use ruvector_mincut::prelude::*;
use ruvector_mincut::wrapper::MinCutWrapper;
use std::sync::Arc;
/// Generate a random graph with n vertices and m edges
fn generate_random_edges(n: usize, m: usize, seed: u64) -> Vec<(u64, u64)> {
let mut rng = StdRng::seed_from_u64(seed);
let mut edges = Vec::with_capacity(m);
let mut seen = std::collections::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 seen.insert(key) {
edges.push((u, v));
}
}
}
edges
}
/// Generate path graph edges
fn generate_path_edges(n: usize) -> Vec<(u64, u64)> {
(0..n as u64 - 1).map(|i| (i, i + 1)).collect()
}
/// Generate cycle graph edges
fn generate_cycle_edges(n: usize) -> Vec<(u64, u64)> {
let mut edges: Vec<_> = (0..n as u64 - 1).map(|i| (i, i + 1)).collect();
edges.push((n as u64 - 1, 0));
edges
}
/// Generate complete graph edges
fn generate_complete_edges(n: usize) -> Vec<(u64, u64)> {
let mut edges = Vec::new();
for i in 0..n as u64 {
for j in (i + 1)..n as u64 {
edges.push((i, j));
}
}
edges
}
/// Generate grid graph edges
fn generate_grid_edges(width: usize, height: usize) -> Vec<(u64, u64)> {
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));
}
if i + 1 < height {
edges.push((v, v + (width as u64)));
}
}
}
edges
}
fn benchmark_insert_edge(c: &mut Criterion) {
let mut group = c.benchmark_group("bounded_insert_edge");
for &size in &[100, 500, 1000, 5000] {
group.throughput(Throughput::Elements(1));
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
b.iter_batched(
|| {
// Setup: create graph and wrapper with existing edges
let graph = Arc::new(DynamicGraph::new());
let edges = generate_path_edges(size);
for (i, (u, v)) in edges.iter().enumerate() {
graph.insert_edge(*u, *v, 1.0).unwrap();
}
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
for (i, (u, v)) in edges.iter().enumerate() {
wrapper.insert_edge(i as u64, *u, *v);
}
// Prepare new edge to insert
let new_u = 0;
let new_v = size as u64 / 2;
(graph, wrapper, new_u, new_v, size as u64)
},
|(graph, mut wrapper, u, v, edge_id)| {
// Benchmark: single insert operation
if graph.insert_edge(u, v, 1.0).is_ok() {
wrapper.insert_edge(edge_id, u, v);
}
black_box(wrapper)
},
criterion::BatchSize::SmallInput,
);
});
}
group.finish();
}
fn benchmark_delete_edge(c: &mut Criterion) {
let mut group = c.benchmark_group("bounded_delete_edge");
for &size in &[100, 500, 1000] {
group.throughput(Throughput::Elements(1));
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
b.iter_batched(
|| {
// Setup: create graph with cycle so deletion doesn't disconnect
let graph = Arc::new(DynamicGraph::new());
let edges = generate_cycle_edges(size);
for (u, v) in &edges {
graph.insert_edge(*u, *v, 1.0).unwrap();
}
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
for (i, (u, v)) in edges.iter().enumerate() {
wrapper.insert_edge(i as u64, *u, *v);
}
// Edge to delete
let del_u = 0;
let del_v = 1;
(graph, wrapper, del_u, del_v)
},
|(graph, mut wrapper, u, v)| {
// Benchmark: single delete operation
wrapper.delete_edge(0, u, v);
black_box(wrapper)
},
criterion::BatchSize::SmallInput,
);
});
}
group.finish();
}
fn benchmark_query(c: &mut Criterion) {
let mut group = c.benchmark_group("bounded_query");
for &size in &[100, 500, 1000, 5000] {
group.throughput(Throughput::Elements(1));
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
b.iter_batched(
|| {
// Setup: build path graph
let graph = Arc::new(DynamicGraph::new());
let edges = generate_path_edges(size);
for (u, v) in &edges {
graph.insert_edge(*u, *v, 1.0).unwrap();
}
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
for (i, (u, v)) in edges.iter().enumerate() {
wrapper.insert_edge(i as u64, *u, *v);
}
wrapper
},
|mut wrapper| {
// Benchmark: query operation
let result = wrapper.query();
black_box(result)
},
criterion::BatchSize::SmallInput,
);
});
}
group.finish();
}
fn benchmark_query_after_updates(c: &mut Criterion) {
let mut group = c.benchmark_group("bounded_query_after_updates");
for &num_updates in &[10, 50, 100, 500] {
group.throughput(Throughput::Elements(num_updates as u64));
group.bench_with_input(
BenchmarkId::from_parameter(num_updates),
&num_updates,
|b, &num_updates| {
b.iter_batched(
|| {
// Setup: build base graph
let graph = Arc::new(DynamicGraph::new());
let base_size = 500;
let edges = generate_path_edges(base_size);
for (u, v) in &edges {
graph.insert_edge(*u, *v, 1.0).unwrap();
}
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
for (i, (u, v)) in edges.iter().enumerate() {
wrapper.insert_edge(i as u64, *u, *v);
}
// Add buffered updates
let mut rng = StdRng::seed_from_u64(42);
let mut edge_id = base_size as u64;
for _ in 0..num_updates {
let u = rng.gen_range(0..base_size as u64);
let v = rng.gen_range(0..base_size as u64);
if u != v && graph.insert_edge(u, v, 1.0).is_ok() {
wrapper.insert_edge(edge_id, u, v);
edge_id += 1;
}
}
wrapper
},
|mut wrapper| {
// Benchmark: query with buffered updates
let result = wrapper.query();
black_box(result)
},
criterion::BatchSize::SmallInput,
);
},
);
}
group.finish();
}
fn benchmark_different_topologies(c: &mut Criterion) {
let mut group = c.benchmark_group("bounded_topologies");
let size = 500;
// Path graph
group.bench_function("path", |b| {
b.iter_batched(
|| {
let graph = Arc::new(DynamicGraph::new());
let edges = generate_path_edges(size);
for (u, v) in &edges {
graph.insert_edge(*u, *v, 1.0).unwrap();
}
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
for (i, (u, v)) in edges.iter().enumerate() {
wrapper.insert_edge(i as u64, *u, *v);
}
wrapper
},
|mut wrapper| {
let result = wrapper.query();
black_box(result)
},
criterion::BatchSize::SmallInput,
);
});
// Cycle graph
group.bench_function("cycle", |b| {
b.iter_batched(
|| {
let graph = Arc::new(DynamicGraph::new());
let edges = generate_cycle_edges(size);
for (u, v) in &edges {
graph.insert_edge(*u, *v, 1.0).unwrap();
}
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
for (i, (u, v)) in edges.iter().enumerate() {
wrapper.insert_edge(i as u64, *u, *v);
}
wrapper
},
|mut wrapper| {
let result = wrapper.query();
black_box(result)
},
criterion::BatchSize::SmallInput,
);
});
// Grid graph
let grid_size = 22; // ~484 vertices
group.bench_function("grid", |b| {
b.iter_batched(
|| {
let graph = Arc::new(DynamicGraph::new());
let edges = generate_grid_edges(grid_size, grid_size);
for (u, v) in &edges {
graph.insert_edge(*u, *v, 1.0).unwrap();
}
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
for (i, (u, v)) in edges.iter().enumerate() {
wrapper.insert_edge(i as u64, *u, *v);
}
wrapper
},
|mut wrapper| {
let result = wrapper.query();
black_box(result)
},
criterion::BatchSize::SmallInput,
);
});
// Complete graph (small due to O(n^2) edges)
let complete_size = 30;
group.bench_function("complete", |b| {
b.iter_batched(
|| {
let graph = Arc::new(DynamicGraph::new());
let edges = generate_complete_edges(complete_size);
for (u, v) in &edges {
graph.insert_edge(*u, *v, 1.0).unwrap();
}
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
for (i, (u, v)) in edges.iter().enumerate() {
wrapper.insert_edge(i as u64, *u, *v);
}
wrapper
},
|mut wrapper| {
let result = wrapper.query();
black_box(result)
},
criterion::BatchSize::SmallInput,
);
});
group.finish();
}
fn benchmark_mixed_workload(c: &mut Criterion) {
let mut group = c.benchmark_group("bounded_mixed_workload");
group.bench_function("realistic_workload", |b| {
b.iter_batched(
|| {
let graph = Arc::new(DynamicGraph::new());
let base_size = 1000;
let edges = generate_random_edges(base_size, base_size * 2, 42);
for (u, v) in &edges {
graph.insert_edge(*u, *v, 1.0).unwrap();
}
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
for (i, (u, v)) in edges.iter().enumerate() {
wrapper.insert_edge(i as u64, *u, *v);
}
(graph, wrapper)
},
|(graph, mut wrapper)| {
let mut rng = StdRng::seed_from_u64(12345);
let mut edge_id = 2000u64;
// Simulate realistic workload:
// 70% queries, 20% inserts, 10% deletes
for _ in 0..100 {
let op = rng.gen_range(0..10);
if op < 7 {
// Query
let _ = black_box(wrapper.query());
} else if op < 9 {
// Insert
let u = rng.gen_range(0..1000);
let v = rng.gen_range(0..1000);
if u != v && graph.insert_edge(u, v, 1.0).is_ok() {
wrapper.insert_edge(edge_id, u, v);
edge_id += 1;
}
} else {
// Delete (try to delete a random edge)
let u = rng.gen_range(0..1000);
let v = rng.gen_range(0..1000);
if graph.delete_edge(u, v).is_ok() {
wrapper.delete_edge(edge_id, u, v);
edge_id += 1;
}
}
}
black_box((graph, wrapper))
},
criterion::BatchSize::SmallInput,
);
});
group.finish();
}
fn benchmark_lazy_instantiation(c: &mut Criterion) {
let mut group = c.benchmark_group("bounded_lazy_instantiation");
group.bench_function("first_query", |b| {
b.iter_batched(
|| {
// Setup: wrapper with no instances created yet
let graph = Arc::new(DynamicGraph::new());
let edges = generate_path_edges(500);
for (u, v) in &edges {
graph.insert_edge(*u, *v, 1.0).unwrap();
}
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
for (i, (u, v)) in edges.iter().enumerate() {
wrapper.insert_edge(i as u64, *u, *v);
}
assert_eq!(wrapper.num_instances(), 0, "No instances before query");
wrapper
},
|mut wrapper| {
// Benchmark: first query triggers instantiation
let result = wrapper.query();
black_box(result)
},
criterion::BatchSize::SmallInput,
);
});
group.bench_function("subsequent_query", |b| {
b.iter_batched(
|| {
// Setup: wrapper with instances already created
let graph = Arc::new(DynamicGraph::new());
let edges = generate_path_edges(500);
for (u, v) in &edges {
graph.insert_edge(*u, *v, 1.0).unwrap();
}
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
for (i, (u, v)) in edges.iter().enumerate() {
wrapper.insert_edge(i as u64, *u, *v);
}
// Trigger initial instantiation
let _ = wrapper.query();
assert!(
wrapper.num_instances() > 0,
"Instances created after first query"
);
wrapper
},
|mut wrapper| {
// Benchmark: subsequent query (instances already exist)
let result = wrapper.query();
black_box(result)
},
criterion::BatchSize::SmallInput,
);
});
group.finish();
}
criterion_group!(
benches,
benchmark_insert_edge,
benchmark_delete_edge,
benchmark_query,
benchmark_query_after_updates,
benchmark_different_topologies,
benchmark_mixed_workload,
benchmark_lazy_instantiation,
);
criterion_main!(benches);