git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
548 lines
18 KiB
Rust
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);
|