Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,248 @@
# RuVector MinCut Benchmarks
Comprehensive benchmark suite for the dynamic minimum cut algorithm implementation.
## Overview
This benchmark suite measures the performance characteristics of the dynamic graph operations that underpin the minimum cut algorithm, including:
- **Edge Operations**: Insert/delete throughput and scaling
- **Query Performance**: Connectivity, degree, edge existence checks
- **Workload Patterns**: Mixed operations, batch processing
- **Scaling Analysis**: Verification of subpolynomial update time (O(n^(2/3)))
- **Graph Types**: Random, grid, complete, sparse, and dense graphs
## Running Benchmarks
### Run All Benchmarks
```bash
cd crates/ruvector-mincut
cargo bench --bench mincut_bench
```
### Run Specific Benchmark Groups
```bash
# Graph operations (insert, delete, queries)
cargo bench --bench mincut_bench graph_ops
# Query operations
cargo bench --bench mincut_bench queries
# Workload patterns
cargo bench --bench mincut_bench workloads
# Scaling analysis
cargo bench --bench mincut_bench scaling
```
### Run Individual Benchmarks
```bash
# Insert edge performance
cargo bench --bench mincut_bench -- insert_edge
# Delete edge performance
cargo bench --bench mincut_bench -- delete_edge
# Mixed workload
cargo bench --bench mincut_bench -- mixed_workload
# Scaling analysis
cargo bench --bench mincut_bench -- scaling_analysis
```
### Filter by Size
```bash
# Benchmark only size 1000
cargo bench --bench mincut_bench -- /1000
# Benchmark sizes 100 and 5000
cargo bench --bench mincut_bench -- "/100|5000"
```
## Benchmark Groups
### 1. Graph Operations (`graph_ops`)
- **`insert_edge`**: Edge insertion throughput at various graph sizes (100 to 10K vertices)
- **`delete_edge`**: Edge deletion throughput
- **`degree_query`**: Vertex degree query performance
- **`has_edge_query`**: Edge existence check performance
### 2. Query Operations (`queries`)
- **`query_connectivity`**: Graph connectivity checking (BFS-based)
- **`stats_computation`**: Graph statistics calculation
- **`connected_components`**: Connected components computation
- **`neighbors_iteration`**: Neighbor list retrieval
### 3. Workload Patterns (`workloads`)
- **`mixed_workload`**: Realistic mixed operations (50% insert, 30% delete, 20% query)
- **`batch_operations`**: Batch insertion performance (10 to 1000 edges)
### 4. Scaling Analysis (`scaling`)
- **`scaling_analysis`**: Verify subpolynomial O(n^(2/3)) update time
- Tests sizes: 100, 316, 1K, 3.2K, 10K vertices
- Both insert and delete operations
- **`graph_types`**: Performance across different graph structures
- Random graphs
- Grid graphs (structured)
- Complete graphs (dense)
- Sparse graphs (avg degree ~4)
- Dense graphs (~30% edge density)
- **`memory_efficiency`**: Graph creation and memory footprint
## Graph Generators
The benchmark suite includes several graph generators:
- **Random Graph**: `generate_random_graph(n, m, seed)` - n vertices, m random edges
- **Grid Graph**: `generate_grid_graph(width, height)` - Structured 2D grid
- **Complete Graph**: `generate_complete_graph(n)` - All possible edges
- **Sparse Graph**: `generate_sparse_graph(n, seed)` - Average degree ~4
- **Dense Graph**: `generate_dense_graph(n, seed)` - ~30% edge density
## Expected Performance Characteristics
Based on the theoretical analysis:
### Update Operations (Insert/Delete)
- **Time Complexity**: O(n^(2/3)) amortized
- **Scaling**: Should show subpolynomial growth
- **Throughput**: Higher for smaller graphs
### Query Operations
- **Connectivity**: O(n + m) via BFS
- **Degree**: O(1) via adjacency list lookup
- **Has Edge**: O(1) via hash table lookup
- **Connected Components**: O(n + m) via BFS
### Graph Types
- **Sparse graphs**: Better cache locality, faster updates
- **Dense graphs**: More edges to maintain, slower updates
- **Grid graphs**: Good locality, predictable performance
- **Complete graphs**: Maximum edges, highest overhead
## Interpreting Results
### Scaling Verification
The `scaling_analysis` benchmarks test sizes: 100, 316, 1000, 3162, 10000
For O(n^(2/3)) complexity:
- 100 → 316 (3.16x): expect ~2.1x slowdown
- 316 → 1000 (3.16x): expect ~2.1x slowdown
- 1000 → 3162 (3.16x): expect ~2.1x slowdown
- 3162 → 10000 (3.16x): expect ~2.1x slowdown
If you observe linear or worse scaling, there may be algorithmic issues.
### Throughput Metrics
Criterion reports:
- **Time/iteration**: Lower is better
- **Throughput**: Higher is better
- **R²**: Close to 1.0 indicates stable measurements
### Performance Baseline
Typical results on modern hardware (approximate):
- Small graphs (100-500): ~100-500 ns/operation
- Medium graphs (1K-5K): ~500ns-2μs/operation
- Large graphs (10K+): ~2-10μs/operation
## Customizing Benchmarks
### Adjust Sample Sizes
Modify `group.sample_size()` in the benchmark code:
```rust
let mut group = c.benchmark_group("my_group");
group.sample_size(50); // Default is 100
```
### Add Custom Graph Sizes
Edit the size arrays in each benchmark:
```rust
for size in [100, 500, 1000, 2500, 5000, 10000].iter() {
// ...
}
```
### Change Workload Mix
Modify the `bench_mixed_workload` ratios:
```rust
match op {
0..=5 => { /* 60% inserts */ },
6..=8 => { /* 30% deletes */ },
_ => { /* 10% queries */ }
}
```
## Output
Benchmark results are saved to:
- `target/criterion/`: HTML reports with graphs
- `target/criterion/{benchmark_name}/report/index.html`: Detailed reports
Open in browser:
```bash
open target/criterion/report/index.html
```
## Continuous Integration
Add to CI pipeline:
```yaml
- name: Run benchmarks
run: |
cd crates/ruvector-mincut
cargo bench --bench mincut_bench -- --output-format bencher | tee bench_output.txt
```
Compare against baseline:
```bash
cargo install critcmp
critcmp baseline current
```
## Troubleshooting
### Benchmarks Take Too Long
Reduce sample size or iterations:
```rust
group.sample_size(10);
group.measurement_time(Duration::from_secs(5));
```
### Unstable Results
- Close other applications
- Disable CPU frequency scaling
- Run with `nice -n -20` for higher priority
- Check system load: `uptime`
### Out of Memory
Reduce graph sizes:
```rust
for size in [100, 500, 1000].iter() { // Skip 5K and 10K
```
## References
- [Criterion.rs Documentation](https://bheisler.github.io/criterion.rs/book/)
- Dynamic Min-Cut Algorithm: O(n^(2/3)) update time
- Graph Connectivity: BFS in O(n + m)

View File

@@ -0,0 +1,496 @@
//! 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);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,547 @@
//! 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);

View File

@@ -0,0 +1,368 @@
//! Benchmark suite for j-Tree + BMSSP optimizations
//!
//! Measures before/after performance for each optimization:
//! - DSpar: 5.9x target
//! - Cache: 10x target
//! - SIMD: 2-4x target
//! - Pool: 50-75% memory reduction
//! - Parallel: Near-linear scaling
//! - WASM Batch: 10x FFI reduction
//!
//! Target: Combined 10x speedup
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use ruvector_mincut::graph::DynamicGraph;
use ruvector_mincut::optimization::{
BatchConfig, BenchmarkSuite, CacheConfig, DegreePresparse, DistanceArray, LevelData, LevelPool,
LevelUpdateResult, ParallelConfig, ParallelLevelUpdater, PathDistanceCache, PoolConfig,
PresparseConfig, SimdDistanceOps, WasmBatchOps,
};
use std::collections::HashSet;
/// Create test graph with specified size
fn create_test_graph(vertices: usize, edges: usize) -> DynamicGraph {
let graph = DynamicGraph::new();
for i in 0..vertices {
graph.add_vertex(i as u64);
}
let mut edge_count = 0;
for i in 0..vertices {
for j in (i + 1)..vertices {
if edge_count >= edges {
break;
}
let _ = graph.insert_edge(i as u64, j as u64, 1.0);
edge_count += 1;
}
if edge_count >= edges {
break;
}
}
graph
}
/// Benchmark DSpar sparsification
fn bench_dspar(c: &mut Criterion) {
let mut group = c.benchmark_group("DSpar");
for size in [100, 1000, 5000].iter() {
let graph = create_test_graph(*size, size * 5);
group.bench_with_input(BenchmarkId::new("baseline", size), size, |b, _| {
b.iter(|| {
let edges: Vec<_> = graph.edges().collect();
black_box(edges.len())
})
});
let mut dspar = DegreePresparse::with_config(PresparseConfig {
target_sparsity: 0.1,
..Default::default()
});
group.bench_with_input(BenchmarkId::new("optimized", size), size, |b, _| {
b.iter(|| {
let result = dspar.presparse(&graph);
black_box(result.edges.len())
})
});
}
group.finish();
}
/// Benchmark path distance cache
fn bench_cache(c: &mut Criterion) {
let mut group = c.benchmark_group("PathCache");
for size in [100, 1000, 5000].iter() {
group.bench_with_input(
BenchmarkId::new("baseline_no_cache", size),
size,
|b, &size| {
b.iter(|| {
let mut total = 0.0;
for i in 0..size {
total += (i as f64 * 1.414).sqrt();
}
black_box(total)
})
},
);
let cache = PathDistanceCache::with_config(CacheConfig {
max_entries: *size,
..Default::default()
});
// Pre-populate cache
for i in 0..*size {
cache.insert(i as u64, (i + 1) as u64, (i as f64).sqrt());
}
group.bench_with_input(
BenchmarkId::new("optimized_with_cache", size),
size,
|b, &size| {
b.iter(|| {
let mut total = 0.0;
for i in 0..size {
if let Some(d) = cache.get(i as u64, (i + 1) as u64) {
total += d;
}
}
black_box(total)
})
},
);
}
group.finish();
}
/// Benchmark SIMD distance operations
fn bench_simd(c: &mut Criterion) {
let mut group = c.benchmark_group("SIMD");
for size in [100, 1000, 10000].iter() {
let mut arr = DistanceArray::new(*size);
for i in 0..*size {
arr.set(i as u64, (i as f64) * 0.5 + 1.0);
}
arr.set((*size / 2) as u64, 0.1);
group.bench_with_input(BenchmarkId::new("find_min_naive", size), &arr, |b, arr| {
b.iter(|| {
let data = arr.as_slice();
let mut min_val = f64::INFINITY;
let mut min_idx = 0;
for (i, &d) in data.iter().enumerate() {
if d < min_val {
min_val = d;
min_idx = i;
}
}
black_box((min_val, min_idx))
})
});
group.bench_with_input(BenchmarkId::new("find_min_simd", size), &arr, |b, arr| {
b.iter(|| black_box(SimdDistanceOps::find_min(arr)))
});
let neighbors: Vec<_> = (0..(size / 10).min(100))
.map(|i| ((i * 10) as u64, 1.0))
.collect();
group.bench_with_input(
BenchmarkId::new("relax_batch_naive", size),
size,
|b, &size| {
let mut arr = DistanceArray::new(size);
b.iter(|| {
let data = arr.as_mut_slice();
for &(idx, weight) in &neighbors {
let idx = idx as usize;
if idx < data.len() {
let new_dist = 0.0 + weight;
if new_dist < data[idx] {
data[idx] = new_dist;
}
}
}
black_box(())
})
},
);
group.bench_with_input(
BenchmarkId::new("relax_batch_simd", size),
size,
|b, &size| {
let mut arr = DistanceArray::new(size);
b.iter(|| black_box(SimdDistanceOps::relax_batch(&mut arr, 0.0, &neighbors)))
},
);
}
group.finish();
}
/// Benchmark pool allocation
fn bench_pool(c: &mut Criterion) {
let mut group = c.benchmark_group("Pool");
for size in [100, 1000].iter() {
group.bench_with_input(
BenchmarkId::new("baseline_alloc_dealloc", size),
size,
|b, &size| {
b.iter(|| {
let mut levels = Vec::new();
for i in 0..10 {
levels.push(LevelData::new(i, size));
}
black_box(levels.len())
})
},
);
let pool = LevelPool::with_config(PoolConfig {
max_materialized_levels: 5,
lazy_dealloc: true,
..Default::default()
});
group.bench_with_input(
BenchmarkId::new("optimized_pool", size),
size,
|b, &size| {
b.iter(|| {
for i in 0..10 {
let level = pool.allocate_level(i, size);
pool.materialize(i, level);
}
black_box(pool.stats().materialized_levels)
})
},
);
}
group.finish();
}
/// Benchmark parallel processing
fn bench_parallel(c: &mut Criterion) {
let mut group = c.benchmark_group("Parallel");
let levels: Vec<usize> = (0..100).collect();
for work_size in [10, 100, 1000].iter() {
group.bench_with_input(
BenchmarkId::new("sequential", work_size),
work_size,
|b, &work_size| {
b.iter(|| {
let _results: Vec<_> = levels
.iter()
.map(|&level| {
let mut sum = 0.0;
for i in 0..work_size {
sum += (i as f64).sqrt();
}
LevelUpdateResult {
level,
cut_value: sum,
partition: HashSet::new(),
time_us: 0,
}
})
.collect();
black_box(())
})
},
);
let updater = ParallelLevelUpdater::with_config(ParallelConfig {
min_parallel_size: 10,
..Default::default()
});
group.bench_with_input(
BenchmarkId::new("parallel_rayon", work_size),
work_size,
|b, &work_size| {
b.iter(|| {
let _results = updater.process_parallel(&levels, |level| {
let mut sum = 0.0;
for i in 0..work_size {
sum += (i as f64).sqrt();
}
LevelUpdateResult {
level,
cut_value: sum,
partition: HashSet::new(),
time_us: 0,
}
});
black_box(())
})
},
);
}
group.finish();
}
/// Benchmark WASM batch operations
fn bench_wasm_batch(c: &mut Criterion) {
let mut group = c.benchmark_group("WASM_Batch");
for size in [100, 1000, 5000].iter() {
let edges: Vec<_> = (0..*size)
.map(|i| (i as u64, (i + 1) as u64, 1.0))
.collect();
group.bench_with_input(
BenchmarkId::new("individual_ops", size),
&edges,
|b, edges| {
b.iter(|| {
for edge in edges {
black_box(edge);
}
})
},
);
let mut batch = WasmBatchOps::with_config(BatchConfig {
max_batch_size: 1024,
..Default::default()
});
group.bench_with_input(BenchmarkId::new("batched_ops", size), &edges, |b, edges| {
b.iter(|| {
batch.queue_insert_edges(edges.clone());
let results = batch.execute_batch();
black_box(results.len())
})
});
}
group.finish();
}
/// Run complete benchmark suite
fn bench_complete_suite(c: &mut Criterion) {
let mut group = c.benchmark_group("Complete_Suite");
group.bench_function("full_optimization_suite", |b| {
b.iter(|| {
let mut suite = BenchmarkSuite::new()
.with_sizes(vec![100])
.with_iterations(1);
let results = suite.run_all();
let combined = suite.combined_speedup();
black_box((results.len(), combined))
})
});
group.finish();
}
criterion_group!(
benches,
bench_dspar,
bench_cache,
bench_simd,
bench_pool,
bench_parallel,
bench_wasm_batch,
bench_complete_suite,
);
criterion_main!(benches);

View File

@@ -0,0 +1,305 @@
//! Benchmarks for 2025 paper algorithm implementations
//!
//! Tests performance of:
//! - PolylogConnectivity (arXiv:2510.08297)
//! - ApproxMinCut (SODA 2025, arXiv:2412.15069)
//! - CacheOptBFS
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use ruvector_mincut::connectivity::cache_opt::{BatchProcessor, CacheOptAdjacency, CacheOptBFS};
use ruvector_mincut::{
ApproxMinCut, ApproxMinCutConfig, DynamicConnectivity, PolylogConnectivity, PolylogStats,
};
/// Generate a random graph with n vertices and m edges
fn generate_graph(n: usize, m: usize, seed: u64) -> Vec<(u64, u64)> {
let mut edges = Vec::with_capacity(m);
let mut rng = seed;
for _ in 0..m {
// Simple LCG for deterministic "random" numbers
rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
let u = (rng % n as u64) as u64;
rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
let v = (rng % n as u64) as u64;
if u != v {
edges.push((u, v));
}
}
edges
}
/// Generate weighted graph edges
fn generate_weighted_graph(n: usize, m: usize, seed: u64) -> Vec<(u64, u64, f64)> {
let mut edges = Vec::with_capacity(m);
let mut rng = seed;
for _ in 0..m {
rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
let u = (rng % n as u64) as u64;
rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
let v = (rng % n as u64) as u64;
rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
let w = ((rng % 100) as f64 + 1.0) / 10.0;
if u != v {
edges.push((u, v, w));
}
}
edges
}
// ============================================================================
// PolylogConnectivity Benchmarks
// ============================================================================
fn bench_polylog_insert(c: &mut Criterion) {
let mut group = c.benchmark_group("polylog_insert");
for size in [100, 500, 1000, 5000].iter() {
let edges = generate_graph(*size, size * 2, 42);
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, _| {
b.iter(|| {
let mut conn = PolylogConnectivity::new();
for &(u, v) in &edges {
conn.insert_edge(u, v);
}
black_box(conn.component_count())
});
});
}
group.finish();
}
fn bench_polylog_delete(c: &mut Criterion) {
let mut group = c.benchmark_group("polylog_delete");
for size in [100, 500, 1000].iter() {
let edges = generate_graph(*size, size * 2, 42);
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, _| {
b.iter_batched(
|| {
let mut conn = PolylogConnectivity::new();
for &(u, v) in &edges {
conn.insert_edge(u, v);
}
conn
},
|mut conn| {
// Delete half the edges
for i in 0..edges.len() / 2 {
let (u, v) = edges[i];
conn.delete_edge(u, v);
}
black_box(conn.component_count())
},
criterion::BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_polylog_query(c: &mut Criterion) {
let mut group = c.benchmark_group("polylog_query");
for size in [100, 500, 1000, 5000].iter() {
let edges = generate_graph(*size, size * 2, 42);
let mut conn = PolylogConnectivity::new();
for &(u, v) in &edges {
conn.insert_edge(u, v);
}
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| {
let queries: Vec<(u64, u64)> = (0..100)
.map(|i| {
(
(i * 7) as u64 % size as u64,
(i * 13 + 1) as u64 % size as u64,
)
})
.collect();
b.iter(|| {
let mut count = 0;
for &(u, v) in &queries {
if conn.connected(u, v) {
count += 1;
}
}
black_box(count)
});
});
}
group.finish();
}
// ============================================================================
// ApproxMinCut Benchmarks
// ============================================================================
fn bench_approx_insert(c: &mut Criterion) {
let mut group = c.benchmark_group("approx_mincut_insert");
for size in [100, 500, 1000].iter() {
let edges = generate_weighted_graph(*size, size * 2, 42);
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, _| {
b.iter(|| {
let mut approx = ApproxMinCut::with_epsilon(0.1);
for &(u, v, w) in &edges {
approx.insert_edge(u, v, w);
}
black_box(approx.edge_count())
});
});
}
group.finish();
}
fn bench_approx_query(c: &mut Criterion) {
let mut group = c.benchmark_group("approx_mincut_query");
for size in [50, 100, 200, 500].iter() {
let edges = generate_weighted_graph(*size, size * 2, 42);
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, _| {
b.iter_batched(
|| {
let mut approx = ApproxMinCut::with_epsilon(0.1);
for &(u, v, w) in &edges {
approx.insert_edge(u, v, w);
}
approx
},
|mut approx| black_box(approx.min_cut_value()),
criterion::BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_approx_epsilon_comparison(c: &mut Criterion) {
let mut group = c.benchmark_group("approx_epsilon");
let size = 200;
let edges = generate_weighted_graph(size, size * 2, 42);
for epsilon in [0.05, 0.1, 0.2, 0.5].iter() {
group.bench_with_input(BenchmarkId::from_parameter(epsilon), epsilon, |b, &eps| {
b.iter_batched(
|| {
let mut approx = ApproxMinCut::with_epsilon(eps);
for &(u, v, w) in &edges {
approx.insert_edge(u, v, w);
}
approx
},
|mut approx| black_box(approx.min_cut_value()),
criterion::BatchSize::SmallInput,
);
});
}
group.finish();
}
// ============================================================================
// CacheOptBFS Benchmarks
// ============================================================================
fn bench_cache_opt_bfs(c: &mut Criterion) {
let mut group = c.benchmark_group("cache_opt_bfs");
for size in [100, 500, 1000, 5000].iter() {
let edges: Vec<(u64, u64, f64)> = generate_weighted_graph(*size, size * 3, 42);
let max_v = edges
.iter()
.map(|(u, v, _)| (*u).max(*v))
.max()
.unwrap_or(0);
let adj = CacheOptAdjacency::from_edges(&edges, max_v);
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, _| {
b.iter(|| {
let bfs = CacheOptBFS::new(&adj, 0);
black_box(bfs.run().len())
});
});
}
group.finish();
}
fn bench_batch_processor(c: &mut Criterion) {
let mut group = c.benchmark_group("batch_processor");
for size in [100, 500, 1000, 5000].iter() {
let edges: Vec<(u64, u64, f64)> = generate_weighted_graph(*size, size * 3, 42);
let max_v = edges
.iter()
.map(|(u, v, _)| (*u).max(*v))
.max()
.unwrap_or(0);
let adj = CacheOptAdjacency::from_edges(&edges, max_v);
let vertices: Vec<u64> = (0..=max_v).collect();
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, _| {
let processor = BatchProcessor::new();
b.iter(|| {
let degrees = processor.compute_degrees(&adj, &vertices);
black_box(degrees.len())
});
});
}
group.finish();
}
// ============================================================================
// Comparison: PolylogConnectivity vs DynamicConnectivity
// ============================================================================
fn bench_connectivity_comparison(c: &mut Criterion) {
let mut group = c.benchmark_group("connectivity_comparison");
let size = 500;
let edges = generate_graph(size, size * 2, 42);
// Polylog insert
group.bench_function("polylog_build_500", |b| {
b.iter(|| {
let mut conn = PolylogConnectivity::new();
for &(u, v) in &edges {
conn.insert_edge(u, v);
}
black_box(conn.component_count())
});
});
// Standard DynamicConnectivity insert
group.bench_function("dynamic_build_500", |b| {
b.iter(|| {
let mut conn = DynamicConnectivity::new();
for &(u, v) in &edges {
conn.insert_edge(u, v);
}
black_box(conn.component_count())
});
});
group.finish();
}
criterion_group!(
benches,
bench_polylog_insert,
bench_polylog_delete,
bench_polylog_query,
bench_approx_insert,
bench_approx_query,
bench_approx_epsilon_comparison,
bench_cache_opt_bfs,
bench_batch_processor,
bench_connectivity_comparison,
);
criterion_main!(benches);

View File

@@ -0,0 +1,259 @@
//! Benchmarks for SNN-MinCut Integration
//!
//! Measures:
//! - LIF neuron step performance
//! - STDP weight update throughput
//! - Network propagation latency
//! - Karger-Stein mincut performance
//! - Full cognitive engine throughput
//! - Synchrony computation efficiency
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use ruvector_mincut::graph::DynamicGraph;
use ruvector_mincut::snn::{
compute_synchrony, AttractorConfig, AttractorDynamics, CognitiveMinCutEngine, EngineConfig,
LIFNeuron, LayerConfig, NetworkConfig, NeuronConfig, Spike, SpikeTrain, SpikingNetwork,
SynapseMatrix,
};
/// Generate a random graph for benchmarking
fn create_test_graph(n: usize) -> DynamicGraph {
let graph = DynamicGraph::new();
// Add vertices
for i in 0..n {
graph.add_vertex(i as u64);
}
// Add random edges (sparse graph ~3 edges per vertex)
let mut seed: u64 = 12345;
for i in 0..n {
for _ in 0..3 {
seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
let j = (seed % n as u64) as usize;
if i != j {
let weight = ((seed % 100) as f64) / 100.0 + 0.5;
let _ = graph.insert_edge(i as u64, j as u64, weight);
}
}
}
graph
}
/// Benchmark LIF neuron step
fn bench_lif_neuron(c: &mut Criterion) {
let mut group = c.benchmark_group("lif_neuron");
for size in [100, 1000, 10000].iter() {
group.throughput(Throughput::Elements(*size as u64));
group.bench_with_input(BenchmarkId::new("step", size), size, |b, &size| {
let config = NeuronConfig::default();
let mut neurons: Vec<LIFNeuron> = (0..size)
.map(|i| LIFNeuron::with_config(i, config.clone()))
.collect();
let currents: Vec<f64> = (0..size).map(|i| (i % 10) as f64 * 0.1).collect();
b.iter(|| {
let mut spikes = 0;
for (neuron, &current) in neurons.iter_mut().zip(currents.iter()) {
if neuron.step(black_box(current), 1.0, 0.0) {
spikes += 1;
}
}
black_box(spikes)
});
});
}
group.finish();
}
/// Benchmark STDP weight updates
fn bench_stdp(c: &mut Criterion) {
let mut group = c.benchmark_group("stdp");
for size in [100, 500, 1000].iter() {
let n_synapses = size * size / 10; // ~10% connectivity
group.throughput(Throughput::Elements(n_synapses as u64));
group.bench_with_input(BenchmarkId::new("update", size), size, |b, &size| {
let mut matrix = SynapseMatrix::new(size, size);
// Create sparse connections
let mut seed: u64 = 42;
for i in 0..size {
for _ in 0..size / 10 {
seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
let j = (seed as usize) % size;
matrix.add_synapse(i, j, 0.5);
}
}
b.iter(|| {
// Simulate spike events
for i in 0..size / 10 {
matrix.on_pre_spike(i, black_box(i as f64));
matrix.on_post_spike(i + 1, black_box(i as f64 + 5.0));
}
});
});
}
group.finish();
}
/// Benchmark spiking network propagation
fn bench_network(c: &mut Criterion) {
let mut group = c.benchmark_group("spiking_network");
for &(input, hidden, output) in [(100, 50, 10), (500, 200, 50), (1000, 500, 100)].iter() {
let total = input + hidden + output;
group.throughput(Throughput::Elements(total as u64));
let name = format!("{}-{}-{}", input, hidden, output);
group.bench_with_input(
BenchmarkId::new("step", &name),
&(input, hidden, output),
|b, &(i, h, o)| {
let config = NetworkConfig {
layers: vec![
LayerConfig::new(i),
LayerConfig::new(h),
LayerConfig::new(o),
],
..NetworkConfig::default()
};
let mut network = SpikingNetwork::new(config);
b.iter(|| black_box(network.step()));
},
);
}
group.finish();
}
/// Benchmark attractor dynamics (includes Karger-Stein)
fn bench_attractor(c: &mut Criterion) {
let mut group = c.benchmark_group("attractor_dynamics");
group.sample_size(50); // Fewer samples for slower benchmarks
for size in [50, 100, 200].iter() {
group.throughput(Throughput::Elements(*size as u64));
group.bench_with_input(BenchmarkId::new("step", size), size, |b, &size| {
let graph = create_test_graph(size);
let config = AttractorConfig::default();
let mut attractor = AttractorDynamics::new(graph, config);
b.iter(|| black_box(attractor.step()));
});
}
group.finish();
}
/// Benchmark synchrony computation
fn bench_synchrony(c: &mut Criterion) {
let mut group = c.benchmark_group("synchrony");
for size in [100, 1000, 10000].iter() {
group.throughput(Throughput::Elements(*size as u64));
group.bench_with_input(BenchmarkId::new("compute", size), size, |b, &size| {
// Generate random spikes
let mut seed: u64 = 999;
let spikes: Vec<Spike> = (0..size)
.map(|i| {
seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
Spike {
neuron_id: (seed as usize) % 100,
time: (i as f64) + ((seed % 100) as f64) / 100.0,
}
})
.collect();
b.iter(|| black_box(compute_synchrony(&spikes, 10.0)));
});
}
group.finish();
}
/// Benchmark full cognitive engine
fn bench_cognitive_engine(c: &mut Criterion) {
let mut group = c.benchmark_group("cognitive_engine");
group.sample_size(30); // Fewer samples for complex benchmark
for size in [50, 100].iter() {
group.throughput(Throughput::Elements(*size as u64));
group.bench_with_input(BenchmarkId::new("step", size), size, |b, &size| {
let graph = create_test_graph(size);
let config = EngineConfig::default();
let mut engine = CognitiveMinCutEngine::new(graph, config);
b.iter(|| black_box(engine.step()));
});
group.bench_with_input(BenchmarkId::new("run_10", size), size, |b, &size| {
let graph = create_test_graph(size);
let config = EngineConfig::default();
let mut engine = CognitiveMinCutEngine::new(graph, config);
b.iter(|| black_box(engine.run(10)));
});
}
group.finish();
}
/// Benchmark spike train operations
fn bench_spike_train(c: &mut Criterion) {
let mut group = c.benchmark_group("spike_train");
for size in [100, 1000, 5000].iter() {
group.throughput(Throughput::Elements(*size as u64));
group.bench_with_input(BenchmarkId::new("to_pattern", size), size, |b, &size| {
let mut train = SpikeTrain::new(0);
for i in 0..size {
train.record_spike(i as f64 * 0.5);
}
b.iter(|| black_box(train.to_pattern(0.0, 1.0, 100)));
});
group.bench_with_input(
BenchmarkId::new("cross_correlation", size),
size,
|b, &size| {
let mut train1 = SpikeTrain::new(0);
let mut train2 = SpikeTrain::new(1);
for i in 0..size {
train1.record_spike(i as f64 * 0.5);
train2.record_spike(i as f64 * 0.5 + 2.0);
}
b.iter(|| black_box(train1.cross_correlation(&train2, 50.0, 1.0)));
},
);
}
group.finish();
}
criterion_group!(
benches,
bench_lif_neuron,
bench_stdp,
bench_network,
bench_attractor,
bench_synchrony,
bench_cognitive_engine,
bench_spike_train,
);
criterion_main!(benches);

View File

@@ -0,0 +1,545 @@
//! SOTA (State-of-the-Art) Benchmarks
//!
//! Benchmarks for the advanced optimizations implemented from the December 2025 paper:
//! - Cut size scaling behavior
//! - Graph density impact
//! - Batch operation efficiency
//! - Memory pool performance
//! - Lazy witness benefits
//! - Replacement edge lookup
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use ruvector_mincut::algorithm::ReplacementEdgeIndex;
use ruvector_mincut::instance::witness::LazyWitness;
use ruvector_mincut::pool::BfsPool;
use ruvector_mincut::prelude::*;
use ruvector_mincut::wrapper::MinCutWrapper;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
// ============================================================================
// Graph Generators
// ============================================================================
/// Generate a graph with a specific minimum cut value
/// Creates two cliques connected by k edges (min cut = k)
fn generate_known_mincut(n_per_side: usize, mincut_value: usize, seed: u64) -> Vec<(u64, u64)> {
let mut edges = Vec::new();
let mut rng = StdRng::seed_from_u64(seed);
// First clique: vertices 0 to n_per_side-1
for i in 0..n_per_side as u64 {
for j in (i + 1)..n_per_side as u64 {
edges.push((i, j));
}
}
// Second clique: vertices n_per_side to 2*n_per_side-1
let offset = n_per_side as u64;
for i in 0..n_per_side as u64 {
for j in (i + 1)..n_per_side as u64 {
edges.push((offset + i, offset + j));
}
}
// Connect with exactly mincut_value edges
for k in 0..mincut_value {
let u = rng.gen_range(0..n_per_side as u64);
let v = offset + rng.gen_range(0..n_per_side as u64);
edges.push((u, v));
}
edges
}
/// Generate graph with specified density (0.0 = sparse, 1.0 = complete)
fn generate_density_graph(n: usize, density: f64, seed: u64) -> Vec<(u64, u64)> {
let mut rng = StdRng::seed_from_u64(seed);
let max_edges = n * (n - 1) / 2;
let target_edges = ((max_edges as f64) * density) as usize;
let mut edges = Vec::with_capacity(target_edges);
let mut seen = HashSet::new();
while edges.len() < target_edges {
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
fn generate_path(n: usize) -> Vec<(u64, u64)> {
(0..n as u64 - 1).map(|i| (i, i + 1)).collect()
}
// ============================================================================
// Cut Size Scaling Benchmarks
// ============================================================================
/// Benchmark how performance scales with minimum cut value
fn bench_cut_size_scaling(c: &mut Criterion) {
let mut group = c.benchmark_group("sota_cut_size_scaling");
group.sample_size(50);
let n_per_side = 50; // 100 vertices total
// Test different min-cut values: 1, 2, 4, 8, 16, 32
for mincut in [1, 2, 4, 8, 16, 32] {
group.bench_with_input(
BenchmarkId::new("query_mincut", mincut),
&mincut,
|b, &mincut| {
b.iter_batched(
|| {
let graph = Arc::new(DynamicGraph::new());
let edges = generate_known_mincut(n_per_side, mincut, 42);
for (u, v) in &edges {
let _ = graph.insert_edge(*u, *v, 1.0);
}
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();
}
// ============================================================================
// Density Impact Benchmarks
// ============================================================================
/// Benchmark how graph density affects performance
fn bench_density_impact(c: &mut Criterion) {
let mut group = c.benchmark_group("sota_density_impact");
group.sample_size(30);
let n = 200;
// Test different densities: 0.01, 0.05, 0.1, 0.2, 0.3
for density_pct in [1, 5, 10, 20, 30] {
let density = density_pct as f64 / 100.0;
group.bench_with_input(
BenchmarkId::new("query_density", format!("{}pct", density_pct)),
&density,
|b, &density| {
b.iter_batched(
|| {
let graph = Arc::new(DynamicGraph::new());
let edges = generate_density_graph(n, density, 42);
for (u, v) in &edges {
let _ = graph.insert_edge(*u, *v, 1.0);
}
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();
}
// ============================================================================
// Batch Operation Benchmarks
// ============================================================================
/// Benchmark batch insert vs sequential insert
fn bench_batch_vs_sequential(c: &mut Criterion) {
let mut group = c.benchmark_group("sota_batch_operations");
for batch_size in [10, 50, 100, 500] {
// Sequential inserts
group.throughput(Throughput::Elements(batch_size as u64));
group.bench_with_input(
BenchmarkId::new("sequential_insert", batch_size),
&batch_size,
|b, &batch_size| {
b.iter_batched(
|| {
let graph = Arc::new(DynamicGraph::new());
// Base graph
let base_edges = generate_path(500);
for (u, v) in &base_edges {
let _ = graph.insert_edge(*u, *v, 1.0);
}
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
for (i, (u, v)) in base_edges.iter().enumerate() {
wrapper.insert_edge(i as u64, *u, *v);
}
// New edges to insert
let mut rng = StdRng::seed_from_u64(123);
let new_edges: Vec<_> = (0..batch_size)
.filter_map(|i| {
let u = rng.gen_range(0..500);
let v = rng.gen_range(0..500);
if u != v && graph.insert_edge(u, v, 1.0).is_ok() {
Some((1000 + i as u64, u, v))
} else {
None
}
})
.collect();
(wrapper, new_edges)
},
|(mut wrapper, edges)| {
for (id, u, v) in edges {
wrapper.insert_edge(id, u, v);
}
black_box(wrapper)
},
criterion::BatchSize::SmallInput,
);
},
);
// Batch inserts
group.bench_with_input(
BenchmarkId::new("batch_insert", batch_size),
&batch_size,
|b, &batch_size| {
b.iter_batched(
|| {
let graph = Arc::new(DynamicGraph::new());
let base_edges = generate_path(500);
for (u, v) in &base_edges {
let _ = graph.insert_edge(*u, *v, 1.0);
}
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
for (i, (u, v)) in base_edges.iter().enumerate() {
wrapper.insert_edge(i as u64, *u, *v);
}
let mut rng = StdRng::seed_from_u64(123);
let new_edges: Vec<_> = (0..batch_size)
.filter_map(|i| {
let u = rng.gen_range(0..500);
let v = rng.gen_range(0..500);
if u != v && graph.insert_edge(u, v, 1.0).is_ok() {
Some((1000 + i as u64, u, v))
} else {
None
}
})
.collect();
(wrapper, new_edges)
},
|(mut wrapper, edges)| {
wrapper.batch_insert_edges(&edges);
black_box(wrapper)
},
criterion::BatchSize::SmallInput,
);
},
);
}
group.finish();
}
// ============================================================================
// Memory Pool Benchmarks
// ============================================================================
/// Benchmark BFS with and without pool
fn bench_memory_pool(c: &mut Criterion) {
let mut group = c.benchmark_group("sota_memory_pool");
for size in [100, 500, 1000, 5000] {
// Without pool - allocate fresh each time
group.bench_with_input(BenchmarkId::new("bfs_no_pool", size), &size, |b, &size| {
b.iter(|| {
let mut queue = std::collections::VecDeque::with_capacity(size);
let mut visited = HashSet::with_capacity(size);
let mut results = Vec::with_capacity(size);
// Simulate BFS work
queue.push_back(0u64);
visited.insert(0);
while let Some(v) = queue.pop_front() {
results.push(v);
if results.len() >= size {
break;
}
// Simulate adding neighbors
for next in v + 1..v + 4 {
if visited.insert(next) {
queue.push_back(next);
}
}
}
black_box(results)
});
});
// With pool - reuse allocations
group.bench_with_input(
BenchmarkId::new("bfs_with_pool", size),
&size,
|b, &size| {
b.iter(|| {
let mut res = BfsPool::acquire(size);
// Simulate BFS work
res.queue.push_back(0);
res.visited.insert(0);
while let Some(v) = res.queue.pop_front() {
res.results.push(v);
if res.results.len() >= size {
break;
}
for next in v + 1..v + 4 {
if res.visited.insert(next) {
res.queue.push_back(next);
}
}
}
black_box(res.results.len())
// Resources returned on drop
});
},
);
}
group.finish();
}
// ============================================================================
// Lazy Witness Benchmarks
// ============================================================================
/// Benchmark lazy vs eager witness materialization
fn bench_lazy_witness(c: &mut Criterion) {
let mut group = c.benchmark_group("sota_lazy_witness");
// Build adjacency for materialization
let adjacency = |v: VertexId| -> Vec<VertexId> {
// Simple linear graph for benchmarking
if v == 0 {
vec![1]
} else if v < 999 {
vec![v - 1, v + 1]
} else {
vec![v - 1]
}
};
// Benchmark storing witnesses without materialization
group.bench_function("lazy_store_100", |b| {
b.iter(|| {
let witnesses: Vec<_> = (0..100)
.map(|i| LazyWitness::new(i as u64, 10, i as u64))
.collect();
black_box(witnesses)
});
});
// Benchmark materializing just the best witness
group.bench_function("lazy_materialize_best", |b| {
b.iter_batched(
|| {
// Create 100 lazy witnesses
(0..100)
.map(|i| LazyWitness::new(i as u64, 10, i as u64))
.collect::<Vec<_>>()
},
|witnesses| {
// Find and materialize only the best (smallest boundary)
let best = witnesses.iter().min_by_key(|w| w.boundary_size()).unwrap();
let handle = best.materialize(&adjacency);
black_box(handle)
},
criterion::BatchSize::SmallInput,
);
});
// Benchmark materializing all witnesses
group.bench_function("eager_materialize_all", |b| {
b.iter_batched(
|| {
(0..100)
.map(|i| LazyWitness::new(i as u64, 10, i as u64))
.collect::<Vec<_>>()
},
|witnesses| {
let handles: Vec<_> = witnesses
.iter()
.map(|w| w.materialize(&adjacency))
.collect();
black_box(handles)
},
criterion::BatchSize::SmallInput,
);
});
group.finish();
}
// ============================================================================
// Replacement Edge Benchmarks
// ============================================================================
/// Benchmark replacement edge lookup
fn bench_replacement_edge(c: &mut Criterion) {
let mut group = c.benchmark_group("sota_replacement_edge");
for n in [100, 500, 1000, 5000] {
// Build tree adjacency
let mut tree_adj: HashMap<VertexId, HashSet<VertexId>> = HashMap::new();
for i in 0..n as u64 - 1 {
tree_adj.entry(i).or_default().insert(i + 1);
tree_adj.entry(i + 1).or_default().insert(i);
}
// Add non-tree edges
let mut rng = StdRng::seed_from_u64(42);
let mut idx = ReplacementEdgeIndex::new(n);
// Add tree edges
for i in 0..n as u64 - 1 {
idx.add_tree_edge(i, i + 1);
}
// Add non-tree edges (skip connections)
for _ in 0..n / 5 {
let u = rng.gen_range(0..n as u64);
let v = rng.gen_range(0..n as u64);
if u != v && (u as i64 - v as i64).abs() > 1 {
idx.add_non_tree_edge(u, v);
}
}
group.bench_with_input(BenchmarkId::new("find_replacement", n), &n, |b, _| {
b.iter_batched(
|| idx.clone(),
|mut idx| {
// Find replacement for middle edge
let u = (n / 2) as u64;
let v = u + 1;
let result = idx.find_replacement(u, v, &tree_adj);
black_box(result)
},
criterion::BatchSize::SmallInput,
);
});
}
group.finish();
}
// ============================================================================
// Binary Search Instance Lookup Benchmarks
// ============================================================================
/// Benchmark binary search vs linear instance lookup
fn bench_instance_lookup(c: &mut Criterion) {
let mut group = c.benchmark_group("sota_instance_lookup");
for num_instances in [10, 50, 100, 500] {
// Simulate instance bounds
let instances: Vec<(u64, u64)> = (0..num_instances)
.map(|i| {
let min = ((1.2f64).powi(i as i32)).floor() as u64;
let max = ((1.2f64).powi((i + 1) as i32)).floor() as u64;
(min.max(1), max.max(1))
})
.collect();
// Linear search
group.bench_with_input(
BenchmarkId::new("linear_search", num_instances),
&num_instances,
|b, _| {
b.iter(|| {
let target = 50u64;
let found = instances
.iter()
.position(|(min, max)| target >= *min && target <= *max);
black_box(found)
});
},
);
// Binary search
group.bench_with_input(
BenchmarkId::new("binary_search", num_instances),
&num_instances,
|b, _| {
b.iter(|| {
let target = 50u64;
let found = instances.binary_search_by(|(min, max)| {
if target < *min {
std::cmp::Ordering::Greater
} else if target > *max {
std::cmp::Ordering::Less
} else {
std::cmp::Ordering::Equal
}
});
black_box(found)
});
},
);
}
group.finish();
}
// ============================================================================
// Criterion Groups
// ============================================================================
criterion_group!(cut_scaling, bench_cut_size_scaling,);
criterion_group!(density, bench_density_impact,);
criterion_group!(batch_ops, bench_batch_vs_sequential,);
criterion_group!(memory, bench_memory_pool, bench_lazy_witness,);
criterion_group!(lookup, bench_replacement_edge, bench_instance_lookup,);
criterion_main!(cut_scaling, density, batch_ops, memory, lookup);