Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
196
vendor/ruvector/examples/data/framework/benches/cut_aware_hnsw_bench.rs
vendored
Normal file
196
vendor/ruvector/examples/data/framework/benches/cut_aware_hnsw_bench.rs
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
//! Benchmarks for Cut-Aware HNSW
|
||||
//!
|
||||
//! Compares performance of gated vs ungated search, and demonstrates
|
||||
//! the trade-offs between coherence-aware navigation and raw search speed.
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion, BenchmarkId};
|
||||
use ruvector_data_framework::cut_aware_hnsw::{CutAwareHNSW, CutAwareConfig};
|
||||
|
||||
fn create_test_index(n: usize, dimension: usize) -> CutAwareHNSW {
|
||||
let config = CutAwareConfig {
|
||||
m: 16,
|
||||
ef_construction: 100,
|
||||
ef_search: 50,
|
||||
coherence_gate_threshold: 0.3,
|
||||
max_cross_cut_hops: 2,
|
||||
enable_cut_pruning: false,
|
||||
cut_recompute_interval: 50,
|
||||
min_zone_size: 5,
|
||||
};
|
||||
|
||||
let mut index = CutAwareHNSW::new(config);
|
||||
|
||||
// Insert vectors in two clusters
|
||||
for i in 0..n {
|
||||
let mut vec = vec![0.0; dimension];
|
||||
|
||||
if i < n / 2 {
|
||||
// First cluster - high values
|
||||
for j in 0..dimension {
|
||||
vec[j] = 1.0 + (i as f32 * 0.01) + (j as f32 * 0.001);
|
||||
}
|
||||
} else {
|
||||
// Second cluster - low values
|
||||
for j in 0..dimension {
|
||||
vec[j] = -1.0 + (i as f32 * 0.01) + (j as f32 * 0.001);
|
||||
}
|
||||
}
|
||||
|
||||
index.insert(i as u32, &vec).unwrap();
|
||||
}
|
||||
|
||||
index
|
||||
}
|
||||
|
||||
fn bench_gated_search(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("gated_search");
|
||||
|
||||
for size in [100, 500, 1000].iter() {
|
||||
let index = create_test_index(*size, 128);
|
||||
let query = vec![1.0; 128];
|
||||
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("gated", size),
|
||||
size,
|
||||
|b, _| {
|
||||
b.iter(|| {
|
||||
let results = index.search_gated(black_box(&query), black_box(10));
|
||||
black_box(results);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("ungated", size),
|
||||
size,
|
||||
|b, _| {
|
||||
b.iter(|| {
|
||||
let results = index.search_ungated(black_box(&query), black_box(10));
|
||||
black_box(results);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_coherent_neighborhood(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("coherent_neighborhood");
|
||||
|
||||
for size in [100, 500].iter() {
|
||||
let index = create_test_index(*size, 128);
|
||||
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("radius_2", size),
|
||||
size,
|
||||
|b, _| {
|
||||
b.iter(|| {
|
||||
let neighbors = index.coherent_neighborhood(black_box(0), black_box(2));
|
||||
black_box(neighbors);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("radius_5", size),
|
||||
size,
|
||||
|b, _| {
|
||||
b.iter(|| {
|
||||
let neighbors = index.coherent_neighborhood(black_box(0), black_box(5));
|
||||
black_box(neighbors);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_zone_computation(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("zone_computation");
|
||||
|
||||
for size in [100, 500, 1000].iter() {
|
||||
let mut index = create_test_index(*size, 128);
|
||||
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("compute_zones", size),
|
||||
size,
|
||||
|b, _| {
|
||||
b.iter(|| {
|
||||
let zones = index.compute_zones();
|
||||
black_box(zones);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_batch_updates(c: &mut Criterion) {
|
||||
use ruvector_data_framework::cut_aware_hnsw::{EdgeUpdate, UpdateKind};
|
||||
|
||||
let mut group = c.benchmark_group("batch_updates");
|
||||
|
||||
for batch_size in [10, 50, 100].iter() {
|
||||
let mut index = create_test_index(100, 128);
|
||||
|
||||
let updates: Vec<EdgeUpdate> = (0..*batch_size)
|
||||
.map(|i| EdgeUpdate {
|
||||
kind: UpdateKind::Insert,
|
||||
u: i as u32,
|
||||
v: (i + 1) as u32,
|
||||
weight: Some(0.8),
|
||||
})
|
||||
.collect();
|
||||
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("batch_update", batch_size),
|
||||
batch_size,
|
||||
|b, _| {
|
||||
b.iter(|| {
|
||||
let stats = index.batch_update(black_box(updates.clone()));
|
||||
black_box(stats);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_cross_zone_search(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("cross_zone_search");
|
||||
|
||||
let mut index = create_test_index(500, 128);
|
||||
index.compute_zones(); // Ensure zones are computed
|
||||
|
||||
let query = vec![0.0; 128]; // Neutral query
|
||||
|
||||
group.bench_function("single_zone", |b| {
|
||||
b.iter(|| {
|
||||
let results = index.cross_zone_search(black_box(&query), black_box(10), &[0]);
|
||||
black_box(results);
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("two_zones", |b| {
|
||||
b.iter(|| {
|
||||
let results = index.cross_zone_search(black_box(&query), black_box(10), &[0, 1]);
|
||||
black_box(results);
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_gated_search,
|
||||
bench_coherent_neighborhood,
|
||||
bench_zone_computation,
|
||||
bench_batch_updates,
|
||||
bench_cross_zone_search,
|
||||
);
|
||||
criterion_main!(benches);
|
||||
Reference in New Issue
Block a user