Squashed 'vendor/ruvector/' content from commit b64c2172

git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
commit d803bfe2b1
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,204 @@
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use ruvector_core::types::{DistanceMetric, SearchQuery};
use ruvector_core::{DbOptions, VectorDB, VectorEntry};
use tempfile::tempdir;
fn bench_batch_insert(c: &mut Criterion) {
let mut group = c.benchmark_group("batch_insert");
for batch_size in [100, 1000, 10000].iter() {
group.bench_with_input(
BenchmarkId::from_parameter(batch_size),
batch_size,
|bench, &size| {
bench.iter_batched(
|| {
// Setup: Create DB and vectors
let dir = tempdir().unwrap();
let mut options = DbOptions::default();
options.storage_path =
dir.path().join("bench.db").to_string_lossy().to_string();
options.dimensions = 128;
options.hnsw_config = None; // Use flat index for faster insertion
let db = VectorDB::new(options).unwrap();
let vectors: Vec<VectorEntry> = (0..size)
.map(|i| VectorEntry {
id: Some(format!("vec_{}", i)),
vector: (0..128).map(|j| ((i + j) as f32) * 0.01).collect(),
metadata: None,
})
.collect();
(db, vectors, dir)
},
|(db, vectors, _dir)| {
// Benchmark: Batch insert
db.insert_batch(black_box(vectors)).unwrap()
},
criterion::BatchSize::LargeInput,
);
},
);
}
group.finish();
}
fn bench_individual_insert_vs_batch(c: &mut Criterion) {
let mut group = c.benchmark_group("individual_vs_batch_insert");
let size = 1000;
// Individual inserts
group.bench_function("individual_1000", |bench| {
bench.iter_batched(
|| {
let dir = tempdir().unwrap();
let mut options = DbOptions::default();
options.storage_path = dir.path().join("bench.db").to_string_lossy().to_string();
options.dimensions = 64;
options.hnsw_config = None;
let db = VectorDB::new(options).unwrap();
let vectors: Vec<VectorEntry> = (0..size)
.map(|i| VectorEntry {
id: Some(format!("vec_{}", i)),
vector: vec![i as f32; 64],
metadata: None,
})
.collect();
(db, vectors, dir)
},
|(db, vectors, _dir)| {
for vector in vectors {
db.insert(black_box(vector)).unwrap();
}
},
criterion::BatchSize::LargeInput,
);
});
// Batch insert
group.bench_function("batch_1000", |bench| {
bench.iter_batched(
|| {
let dir = tempdir().unwrap();
let mut options = DbOptions::default();
options.storage_path = dir.path().join("bench.db").to_string_lossy().to_string();
options.dimensions = 64;
options.hnsw_config = None;
let db = VectorDB::new(options).unwrap();
let vectors: Vec<VectorEntry> = (0..size)
.map(|i| VectorEntry {
id: Some(format!("vec_{}", i)),
vector: vec![i as f32; 64],
metadata: None,
})
.collect();
(db, vectors, dir)
},
|(db, vectors, _dir)| db.insert_batch(black_box(vectors)).unwrap(),
criterion::BatchSize::LargeInput,
);
});
group.finish();
}
fn bench_parallel_searches(c: &mut Criterion) {
let dir = tempdir().unwrap();
let mut options = DbOptions::default();
options.storage_path = dir
.path()
.join("search_bench.db")
.to_string_lossy()
.to_string();
options.dimensions = 128;
options.distance_metric = DistanceMetric::Cosine;
options.hnsw_config = None;
let db = VectorDB::new(options).unwrap();
// Insert test data
let vectors: Vec<VectorEntry> = (0..1000)
.map(|i| VectorEntry {
id: Some(format!("vec_{}", i)),
vector: (0..128).map(|j| ((i + j) as f32) * 0.01).collect(),
metadata: None,
})
.collect();
db.insert_batch(vectors).unwrap();
// Benchmark multiple sequential searches
c.bench_function("sequential_searches_100", |bench| {
bench.iter(|| {
for i in 0..100 {
let query: Vec<f32> = (0..128).map(|j| ((i + j) as f32) * 0.01).collect();
let _ = db
.search(SearchQuery {
vector: black_box(query),
k: 10,
filter: None,
ef_search: None,
})
.unwrap();
}
});
});
}
fn bench_batch_delete(c: &mut Criterion) {
let mut group = c.benchmark_group("batch_delete");
for size in [100, 1000].iter() {
group.bench_with_input(BenchmarkId::from_parameter(size), size, |bench, &size| {
bench.iter_batched(
|| {
// Setup: Create DB with vectors
let dir = tempdir().unwrap();
let mut options = DbOptions::default();
options.storage_path =
dir.path().join("bench.db").to_string_lossy().to_string();
options.dimensions = 32;
options.hnsw_config = None;
let db = VectorDB::new(options).unwrap();
let vectors: Vec<VectorEntry> = (0..size)
.map(|i| VectorEntry {
id: Some(format!("vec_{}", i)),
vector: vec![i as f32; 32],
metadata: None,
})
.collect();
let ids = db.insert_batch(vectors).unwrap();
(db, ids, dir)
},
|(db, ids, _dir)| {
// Benchmark: Delete all
for id in ids {
db.delete(black_box(&id)).unwrap();
}
},
criterion::BatchSize::LargeInput,
);
});
}
group.finish();
}
criterion_group!(
benches,
bench_batch_insert,
bench_individual_insert_vs_batch,
bench_parallel_searches,
bench_batch_delete
);
criterion_main!(benches);

View File

@@ -0,0 +1,474 @@
//! Memory Allocation and Pool Benchmarks
//!
//! This module benchmarks arena allocation, cache-optimized storage,
//! and memory access patterns.
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use ruvector_core::arena::Arena;
use ruvector_core::cache_optimized::SoAVectorStorage;
// ============================================================================
// Arena Allocation Benchmarks
// ============================================================================
fn bench_arena_allocation(c: &mut Criterion) {
let mut group = c.benchmark_group("arena_allocation");
for count in [10, 100, 1000, 10000] {
group.throughput(Throughput::Elements(count));
// Benchmark arena allocation
group.bench_with_input(BenchmarkId::new("arena", count), &count, |bench, &count| {
bench.iter(|| {
let arena = Arena::new(1024 * 1024);
for _ in 0..count {
let _vec = arena.alloc_vec::<f32>(black_box(64));
}
});
});
// Compare with standard Vec allocation
group.bench_with_input(
BenchmarkId::new("std_vec", count),
&count,
|bench, &count| {
bench.iter(|| {
let mut vecs = Vec::with_capacity(count as usize);
for _ in 0..count {
vecs.push(Vec::<f32>::with_capacity(black_box(64)));
}
vecs
});
},
);
}
group.finish();
}
fn bench_arena_allocation_sizes(c: &mut Criterion) {
let mut group = c.benchmark_group("arena_allocation_sizes");
for size in [8, 32, 64, 128, 256, 512, 1024, 4096] {
group.throughput(Throughput::Bytes(size as u64 * 4)); // f32 = 4 bytes
group.bench_with_input(BenchmarkId::new("alloc", size), &size, |bench, &size| {
bench.iter(|| {
let arena = Arena::new(1024 * 1024);
for _ in 0..1000 {
let _vec = arena.alloc_vec::<f32>(black_box(size));
}
});
});
}
group.finish();
}
fn bench_arena_reset_reuse(c: &mut Criterion) {
let mut group = c.benchmark_group("arena_reset_reuse");
for iterations in [10, 100, 1000] {
group.bench_with_input(
BenchmarkId::new("with_reset", iterations),
&iterations,
|bench, &iterations| {
bench.iter(|| {
let arena = Arena::new(1024 * 1024);
for _ in 0..iterations {
// Allocate
for _ in 0..100 {
let _vec = arena.alloc_vec::<f32>(64);
}
// Reset for reuse
arena.reset();
}
});
},
);
group.bench_with_input(
BenchmarkId::new("without_reset", iterations),
&iterations,
|bench, &iterations| {
bench.iter(|| {
for _ in 0..iterations {
let arena = Arena::new(1024 * 1024);
for _ in 0..100 {
let _vec = arena.alloc_vec::<f32>(64);
}
// No reset, create new arena each time
}
});
},
);
}
group.finish();
}
fn bench_arena_push_operations(c: &mut Criterion) {
let mut group = c.benchmark_group("arena_push");
for count in [100, 1000, 10000] {
group.throughput(Throughput::Elements(count));
// Arena push
group.bench_with_input(BenchmarkId::new("arena", count), &count, |bench, &count| {
bench.iter(|| {
let arena = Arena::new(1024 * 1024);
let mut vec = arena.alloc_vec::<f32>(count as usize);
for i in 0..count {
vec.push(black_box(i as f32));
}
vec
});
});
// Standard Vec push
group.bench_with_input(
BenchmarkId::new("std_vec", count),
&count,
|bench, &count| {
bench.iter(|| {
let mut vec = Vec::with_capacity(count as usize);
for i in 0..count {
vec.push(black_box(i as f32));
}
vec
});
},
);
}
group.finish();
}
// ============================================================================
// SoA Vector Storage Benchmarks
// ============================================================================
fn bench_soa_storage_push(c: &mut Criterion) {
let mut group = c.benchmark_group("soa_storage_push");
for dim in [64, 128, 256, 384, 512, 768] {
let vector: Vec<f32> = (0..dim).map(|i| i as f32 * 0.01).collect();
group.throughput(Throughput::Elements(dim as u64));
group.bench_with_input(BenchmarkId::new("soa", dim), &dim, |bench, _| {
bench.iter(|| {
let mut storage = SoAVectorStorage::new(dim, 128);
for _ in 0..1000 {
storage.push(black_box(&vector));
}
storage
});
});
// Compare with Vec<Vec<f32>>
group.bench_with_input(BenchmarkId::new("vec_of_vec", dim), &dim, |bench, _| {
bench.iter(|| {
let mut storage: Vec<Vec<f32>> = Vec::with_capacity(1000);
for _ in 0..1000 {
storage.push(black_box(vector.clone()));
}
storage
});
});
}
group.finish();
}
fn bench_soa_storage_get(c: &mut Criterion) {
let mut group = c.benchmark_group("soa_storage_get");
for dim in [128, 384, 768] {
let mut storage = SoAVectorStorage::new(dim, 128);
for i in 0..10000 {
let vector: Vec<f32> = (0..dim).map(|j| (i * dim + j) as f32 * 0.001).collect();
storage.push(&vector);
}
let mut output = vec![0.0_f32; dim];
group.bench_with_input(BenchmarkId::new("sequential", dim), &dim, |bench, _| {
bench.iter(|| {
for i in 0..10000 {
storage.get(black_box(i), &mut output);
}
});
});
group.bench_with_input(BenchmarkId::new("random", dim), &dim, |bench, _| {
let indices: Vec<usize> = (0..10000).map(|i| (i * 37 + 13) % 10000).collect();
bench.iter(|| {
for &idx in &indices {
storage.get(black_box(idx), &mut output);
}
});
});
}
group.finish();
}
fn bench_soa_dimension_slice(c: &mut Criterion) {
let mut group = c.benchmark_group("soa_dimension_slice");
for dim in [64, 128, 256, 512] {
let mut storage = SoAVectorStorage::new(dim, 128);
for i in 0..10000 {
let vector: Vec<f32> = (0..dim).map(|j| (i * dim + j) as f32 * 0.001).collect();
storage.push(&vector);
}
group.bench_with_input(
BenchmarkId::new("access_all_dims", dim),
&dim,
|bench, &dim| {
bench.iter(|| {
let mut sum = 0.0_f32;
for d in 0..dim {
let slice = storage.dimension_slice(black_box(d));
sum += slice.iter().sum::<f32>();
}
sum
});
},
);
group.bench_with_input(
BenchmarkId::new("access_single_dim", dim),
&dim,
|bench, _| {
bench.iter(|| {
let slice = storage.dimension_slice(black_box(0));
slice.iter().sum::<f32>()
});
},
);
}
group.finish();
}
fn bench_soa_batch_distances(c: &mut Criterion) {
let mut group = c.benchmark_group("soa_batch_distances");
for (dim, count) in [
(128, 1000),
(384, 1000),
(768, 1000),
(128, 10000),
(384, 5000),
] {
let mut storage = SoAVectorStorage::new(dim, 128);
for i in 0..count {
let vector: Vec<f32> = (0..dim)
.map(|j| ((i * dim + j) % 1000) as f32 * 0.001)
.collect();
storage.push(&vector);
}
let query: Vec<f32> = (0..dim).map(|j| j as f32 * 0.002).collect();
let mut distances = vec![0.0_f32; count];
group.throughput(Throughput::Elements(count as u64));
group.bench_with_input(
BenchmarkId::new(format!("{}d_x{}", dim, count), dim),
&dim,
|bench, _| {
bench.iter(|| {
storage.batch_euclidean_distances(black_box(&query), &mut distances);
});
},
);
}
group.finish();
}
// ============================================================================
// Memory Access Pattern Benchmarks
// ============================================================================
fn bench_memory_layout_comparison(c: &mut Criterion) {
let mut group = c.benchmark_group("memory_layout");
let dim = 384;
let count = 10000;
// SoA layout
let mut soa_storage = SoAVectorStorage::new(dim, 128);
for i in 0..count {
let vector: Vec<f32> = (0..dim)
.map(|j| ((i * dim + j) % 1000) as f32 * 0.001)
.collect();
soa_storage.push(&vector);
}
// AoS layout (Vec<Vec<f32>>)
let aos_storage: Vec<Vec<f32>> = (0..count)
.map(|i| {
(0..dim)
.map(|j| ((i * dim + j) % 1000) as f32 * 0.001)
.collect()
})
.collect();
let query: Vec<f32> = (0..dim).map(|j| j as f32 * 0.002).collect();
let mut soa_distances = vec![0.0_f32; count];
group.bench_function("soa_batch_euclidean", |bench| {
bench.iter(|| {
soa_storage.batch_euclidean_distances(black_box(&query), &mut soa_distances);
});
});
group.bench_function("aos_naive_euclidean", |bench| {
bench.iter(|| {
let distances: Vec<f32> = aos_storage
.iter()
.map(|v| {
query
.iter()
.zip(v.iter())
.map(|(a, b)| (a - b) * (a - b))
.sum::<f32>()
.sqrt()
})
.collect();
distances
});
});
group.finish();
}
fn bench_cache_efficiency(c: &mut Criterion) {
let mut group = c.benchmark_group("cache_efficiency");
let dim = 512;
// Test with different vector counts to observe cache effects
for count in [100, 1000, 10000, 50000] {
let mut storage = SoAVectorStorage::new(dim, 128);
for i in 0..count {
let vector: Vec<f32> = (0..dim)
.map(|j| ((i * dim + j) % 1000) as f32 * 0.001)
.collect();
storage.push(&vector);
}
let query: Vec<f32> = (0..dim).map(|j| j as f32 * 0.001).collect();
let mut distances = vec![0.0_f32; count];
group.throughput(Throughput::Elements(count as u64));
group.bench_with_input(
BenchmarkId::new("batch_distance", count),
&count,
|bench, _| {
bench.iter(|| {
storage.batch_euclidean_distances(black_box(&query), &mut distances);
});
},
);
}
group.finish();
}
// ============================================================================
// Growth and Reallocation Benchmarks
// ============================================================================
fn bench_soa_growth(c: &mut Criterion) {
let mut group = c.benchmark_group("soa_growth");
// Test growth from small initial capacity
group.bench_function("grow_from_small", |bench| {
bench.iter(|| {
let mut storage = SoAVectorStorage::new(128, 4); // Very small initial
for i in 0..10000 {
let vector: Vec<f32> = (0..128).map(|j| (i * 128 + j) as f32 * 0.001).collect();
storage.push(black_box(&vector));
}
storage
});
});
// Test with pre-allocated capacity
group.bench_function("preallocated", |bench| {
bench.iter(|| {
let mut storage = SoAVectorStorage::new(128, 16384); // Pre-allocate
for i in 0..10000 {
let vector: Vec<f32> = (0..128).map(|j| (i * 128 + j) as f32 * 0.001).collect();
storage.push(black_box(&vector));
}
storage
});
});
group.finish();
}
// ============================================================================
// Mixed Type Allocation Benchmarks
// ============================================================================
fn bench_arena_mixed_types(c: &mut Criterion) {
let mut group = c.benchmark_group("arena_mixed_types");
group.bench_function("mixed_allocations", |bench| {
bench.iter(|| {
let arena = Arena::new(1024 * 1024);
for _ in 0..100 {
let _f32_vec = arena.alloc_vec::<f32>(black_box(64));
let _f64_vec = arena.alloc_vec::<f64>(black_box(32));
let _u32_vec = arena.alloc_vec::<u32>(black_box(128));
let _u8_vec = arena.alloc_vec::<u8>(black_box(256));
}
});
});
group.bench_function("uniform_allocations", |bench| {
bench.iter(|| {
let arena = Arena::new(1024 * 1024);
for _ in 0..400 {
let _f32_vec = arena.alloc_vec::<f32>(black_box(64));
}
});
});
group.finish();
}
// ============================================================================
// Criterion Groups
// ============================================================================
criterion_group!(
benches,
bench_arena_allocation,
bench_arena_allocation_sizes,
bench_arena_reset_reuse,
bench_arena_push_operations,
bench_soa_storage_push,
bench_soa_storage_get,
bench_soa_dimension_slice,
bench_soa_batch_distances,
bench_memory_layout_comparison,
bench_cache_efficiency,
bench_soa_growth,
bench_arena_mixed_types,
);
criterion_main!(benches);

View File

@@ -0,0 +1,335 @@
//! SIMD Performance Benchmarks
//!
//! This module benchmarks SIMD-optimized distance calculations
//! across various vector dimensions and operation types.
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use ruvector_core::simd_intrinsics::*;
// ============================================================================
// Helper Functions
// ============================================================================
fn generate_vectors(dim: usize) -> (Vec<f32>, Vec<f32>) {
let a: Vec<f32> = (0..dim).map(|i| (i as f32) * 0.01).collect();
let b: Vec<f32> = (0..dim).map(|i| ((i + 100) as f32) * 0.01).collect();
(a, b)
}
fn generate_batch_vectors(dim: usize, count: usize) -> (Vec<f32>, Vec<Vec<f32>>) {
let query: Vec<f32> = (0..dim).map(|i| (i as f32) * 0.01).collect();
let vectors: Vec<Vec<f32>> = (0..count)
.map(|j| (0..dim).map(|i| ((i + j * 10) as f32) * 0.01).collect())
.collect();
(query, vectors)
}
// ============================================================================
// Euclidean Distance Benchmarks
// ============================================================================
fn bench_euclidean_by_dimension(c: &mut Criterion) {
let mut group = c.benchmark_group("euclidean_by_dimension");
for dim in [32, 64, 128, 256, 384, 512, 768, 1024, 1536, 2048] {
let (a, b) = generate_vectors(dim);
group.throughput(Throughput::Elements(dim as u64));
group.bench_with_input(BenchmarkId::new("simd", dim), &dim, |bench, _| {
bench.iter(|| euclidean_distance_simd(black_box(&a), black_box(&b)));
});
}
group.finish();
}
fn bench_euclidean_small_vectors(c: &mut Criterion) {
let mut group = c.benchmark_group("euclidean_small_vectors");
// Test small vector sizes that may not benefit from SIMD
for dim in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 16] {
let (a, b) = generate_vectors(dim);
group.bench_with_input(BenchmarkId::new("simd", dim), &dim, |bench, _| {
bench.iter(|| euclidean_distance_simd(black_box(&a), black_box(&b)));
});
}
group.finish();
}
fn bench_euclidean_non_aligned(c: &mut Criterion) {
let mut group = c.benchmark_group("euclidean_non_aligned");
// Test non-SIMD-aligned sizes
for dim in [31, 33, 63, 65, 127, 129, 255, 257, 383, 385, 511, 513] {
let (a, b) = generate_vectors(dim);
group.bench_with_input(BenchmarkId::new("simd", dim), &dim, |bench, _| {
bench.iter(|| euclidean_distance_simd(black_box(&a), black_box(&b)));
});
}
group.finish();
}
// ============================================================================
// Dot Product Benchmarks
// ============================================================================
fn bench_dot_product_by_dimension(c: &mut Criterion) {
let mut group = c.benchmark_group("dot_product_by_dimension");
for dim in [32, 64, 128, 256, 384, 512, 768, 1024, 1536, 2048] {
let (a, b) = generate_vectors(dim);
group.throughput(Throughput::Elements(dim as u64));
group.bench_with_input(BenchmarkId::new("simd", dim), &dim, |bench, _| {
bench.iter(|| dot_product_simd(black_box(&a), black_box(&b)));
});
}
group.finish();
}
fn bench_dot_product_common_embeddings(c: &mut Criterion) {
let mut group = c.benchmark_group("dot_product_common_embeddings");
// Common embedding model dimensions
let dims = [
(128, "small"),
(384, "all-MiniLM-L6"),
(512, "e5-small"),
(768, "all-mpnet-base"),
(1024, "e5-large"),
(1536, "text-embedding-ada-002"),
(2048, "llama-7b-hidden"),
];
for (dim, name) in dims {
let (a, b) = generate_vectors(dim);
group.bench_with_input(BenchmarkId::new(name, dim), &dim, |bench, _| {
bench.iter(|| dot_product_simd(black_box(&a), black_box(&b)));
});
}
group.finish();
}
// ============================================================================
// Cosine Similarity Benchmarks
// ============================================================================
fn bench_cosine_by_dimension(c: &mut Criterion) {
let mut group = c.benchmark_group("cosine_by_dimension");
for dim in [32, 64, 128, 256, 384, 512, 768, 1024, 1536, 2048] {
let (a, b) = generate_vectors(dim);
group.throughput(Throughput::Elements(dim as u64));
group.bench_with_input(BenchmarkId::new("simd", dim), &dim, |bench, _| {
bench.iter(|| cosine_similarity_simd(black_box(&a), black_box(&b)));
});
}
group.finish();
}
// ============================================================================
// Manhattan Distance Benchmarks
// ============================================================================
fn bench_manhattan_by_dimension(c: &mut Criterion) {
let mut group = c.benchmark_group("manhattan_by_dimension");
for dim in [32, 64, 128, 256, 384, 512, 768, 1024, 1536, 2048] {
let (a, b) = generate_vectors(dim);
group.throughput(Throughput::Elements(dim as u64));
group.bench_with_input(BenchmarkId::new("simd", dim), &dim, |bench, _| {
bench.iter(|| manhattan_distance_simd(black_box(&a), black_box(&b)));
});
}
group.finish();
}
// ============================================================================
// Batch Operations Benchmarks
// ============================================================================
fn bench_batch_euclidean(c: &mut Criterion) {
let mut group = c.benchmark_group("batch_euclidean");
for count in [10, 100, 1000, 10000] {
let (query, vectors) = generate_batch_vectors(384, count);
group.throughput(Throughput::Elements(count as u64));
group.bench_with_input(BenchmarkId::new("384d", count), &count, |bench, _| {
bench.iter(|| {
for v in &vectors {
euclidean_distance_simd(black_box(&query), black_box(v));
}
});
});
}
group.finish();
}
fn bench_batch_dot_product(c: &mut Criterion) {
let mut group = c.benchmark_group("batch_dot_product");
for count in [10, 100, 1000, 10000] {
let (query, vectors) = generate_batch_vectors(768, count);
group.throughput(Throughput::Elements(count as u64));
group.bench_with_input(BenchmarkId::new("768d", count), &count, |bench, _| {
bench.iter(|| {
for v in &vectors {
dot_product_simd(black_box(&query), black_box(v));
}
});
});
}
group.finish();
}
// ============================================================================
// Comparison Benchmarks (All Metrics)
// ============================================================================
fn bench_all_metrics_comparison(c: &mut Criterion) {
let mut group = c.benchmark_group("metrics_comparison");
let dim = 384; // Common embedding dimension
let (a, b) = generate_vectors(dim);
group.bench_function("euclidean", |bench| {
bench.iter(|| euclidean_distance_simd(black_box(&a), black_box(&b)));
});
group.bench_function("dot_product", |bench| {
bench.iter(|| dot_product_simd(black_box(&a), black_box(&b)));
});
group.bench_function("cosine", |bench| {
bench.iter(|| cosine_similarity_simd(black_box(&a), black_box(&b)));
});
group.bench_function("manhattan", |bench| {
bench.iter(|| manhattan_distance_simd(black_box(&a), black_box(&b)));
});
group.finish();
}
// ============================================================================
// Memory Access Pattern Benchmarks
// ============================================================================
fn bench_sequential_vs_random_access(c: &mut Criterion) {
let mut group = c.benchmark_group("access_patterns");
let dim = 512;
let count = 1000;
// Generate vectors
let vectors: Vec<Vec<f32>> = (0..count)
.map(|j| (0..dim).map(|i| ((i + j * 10) as f32) * 0.01).collect())
.collect();
let query: Vec<f32> = (0..dim).map(|i| (i as f32) * 0.01).collect();
// Sequential access indices
let sequential_indices: Vec<usize> = (0..count).collect();
// Random-ish access indices
let random_indices: Vec<usize> = (0..count)
.map(|i| (i * 37 + 13) % count) // Pseudo-random
.collect();
group.bench_function("sequential", |bench| {
bench.iter(|| {
for &idx in &sequential_indices {
euclidean_distance_simd(black_box(&query), black_box(&vectors[idx]));
}
});
});
group.bench_function("random", |bench| {
bench.iter(|| {
for &idx in &random_indices {
euclidean_distance_simd(black_box(&query), black_box(&vectors[idx]));
}
});
});
group.finish();
}
// ============================================================================
// Throughput Measurement
// ============================================================================
fn bench_throughput_ops_per_second(c: &mut Criterion) {
let mut group = c.benchmark_group("throughput");
group.sample_size(50);
for dim in [128, 384, 768, 1536] {
let (a, b) = generate_vectors(dim);
// Report throughput in operations/second
group.bench_with_input(BenchmarkId::new("euclidean_ops", dim), &dim, |bench, _| {
bench.iter(|| {
// Perform 100 operations per iteration
for _ in 0..100 {
euclidean_distance_simd(black_box(&a), black_box(&b));
}
});
});
group.bench_with_input(
BenchmarkId::new("dot_product_ops", dim),
&dim,
|bench, _| {
bench.iter(|| {
for _ in 0..100 {
dot_product_simd(black_box(&a), black_box(&b));
}
});
},
);
}
group.finish();
}
// ============================================================================
// Criterion Groups
// ============================================================================
criterion_group!(
benches,
bench_euclidean_by_dimension,
bench_euclidean_small_vectors,
bench_euclidean_non_aligned,
bench_dot_product_by_dimension,
bench_dot_product_common_embeddings,
bench_cosine_by_dimension,
bench_manhattan_by_dimension,
bench_batch_euclidean,
bench_batch_dot_product,
bench_all_metrics_comparison,
bench_sequential_vs_random_access,
bench_throughput_ops_per_second,
);
criterion_main!(benches);

View File

@@ -0,0 +1,262 @@
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use ruvector_core::arena::Arena;
use ruvector_core::cache_optimized::SoAVectorStorage;
use ruvector_core::distance::*;
use ruvector_core::lockfree::{LockFreeCounter, LockFreeStats, ObjectPool};
use ruvector_core::simd_intrinsics::*;
use ruvector_core::types::DistanceMetric;
use std::sync::Arc;
use std::thread;
// Benchmark SIMD intrinsics vs SimSIMD
fn bench_simd_comparison(c: &mut Criterion) {
let mut group = c.benchmark_group("simd_comparison");
for size in [128, 384, 768, 1536].iter() {
let a: Vec<f32> = (0..*size).map(|i| i as f32 * 0.1).collect();
let b: Vec<f32> = (0..*size).map(|i| (i + 1) as f32 * 0.1).collect();
// Euclidean distance
group.throughput(Throughput::Elements(*size as u64));
group.bench_with_input(
BenchmarkId::new("euclidean_simsimd", size),
size,
|bench, _| {
bench.iter(|| euclidean_distance(black_box(&a), black_box(&b)));
},
);
group.bench_with_input(
BenchmarkId::new("euclidean_avx2", size),
size,
|bench, _| {
bench.iter(|| euclidean_distance_avx2(black_box(&a), black_box(&b)));
},
);
// Dot product
group.bench_with_input(
BenchmarkId::new("dot_product_simsimd", size),
size,
|bench, _| {
bench.iter(|| dot_product_distance(black_box(&a), black_box(&b)));
},
);
group.bench_with_input(
BenchmarkId::new("dot_product_avx2", size),
size,
|bench, _| {
bench.iter(|| dot_product_avx2(black_box(&a), black_box(&b)));
},
);
// Cosine similarity
group.bench_with_input(
BenchmarkId::new("cosine_simsimd", size),
size,
|bench, _| {
bench.iter(|| cosine_distance(black_box(&a), black_box(&b)));
},
);
group.bench_with_input(BenchmarkId::new("cosine_avx2", size), size, |bench, _| {
bench.iter(|| cosine_similarity_avx2(black_box(&a), black_box(&b)));
});
}
group.finish();
}
// Benchmark Structure-of-Arrays vs Array-of-Structures
fn bench_cache_optimization(c: &mut Criterion) {
let mut group = c.benchmark_group("cache_optimization");
let dimensions = 384;
let num_vectors = 10000;
// Prepare data
let vectors: Vec<Vec<f32>> = (0..num_vectors)
.map(|i| (0..dimensions).map(|j| (i * j) as f32 * 0.001).collect())
.collect();
let query: Vec<f32> = (0..dimensions).map(|i| i as f32 * 0.01).collect();
// Array-of-Structures (traditional Vec<Vec<f32>>)
group.bench_function("aos_batch_distance", |bench| {
bench.iter(|| {
let mut distances: Vec<f32> = Vec::with_capacity(num_vectors);
for vector in &vectors {
let dist = euclidean_distance(black_box(&query), black_box(vector));
distances.push(dist);
}
black_box(distances)
});
});
// Structure-of-Arrays
let mut soa_storage = SoAVectorStorage::new(dimensions, num_vectors);
for vector in &vectors {
soa_storage.push(vector);
}
group.bench_function("soa_batch_distance", |bench| {
bench.iter(|| {
let mut distances = vec![0.0; num_vectors];
soa_storage.batch_euclidean_distances(black_box(&query), &mut distances);
black_box(distances)
});
});
group.finish();
}
// Benchmark arena allocation vs standard allocation
fn bench_arena_allocation(c: &mut Criterion) {
let mut group = c.benchmark_group("arena_allocation");
let num_allocations = 1000;
let vec_size = 100;
group.bench_function("standard_allocation", |bench| {
bench.iter(|| {
let mut vecs = Vec::new();
for _ in 0..num_allocations {
let mut v = Vec::with_capacity(vec_size);
for j in 0..vec_size {
v.push(j as f32);
}
vecs.push(v);
}
black_box(vecs)
});
});
group.bench_function("arena_allocation", |bench| {
bench.iter(|| {
let arena = Arena::new(1024 * 1024);
let mut vecs = Vec::new();
for _ in 0..num_allocations {
let mut v = arena.alloc_vec::<f32>(vec_size);
for j in 0..vec_size {
v.push(j as f32);
}
vecs.push(v);
}
black_box(vecs)
});
});
group.finish();
}
// Benchmark lock-free operations vs locked operations
fn bench_lockfree(c: &mut Criterion) {
let mut group = c.benchmark_group("lockfree");
// Counter benchmark
group.bench_function("lockfree_counter_single_thread", |bench| {
let counter = LockFreeCounter::new(0);
bench.iter(|| {
for _ in 0..10000 {
counter.increment();
}
});
});
group.bench_function("lockfree_counter_multi_thread", |bench| {
bench.iter(|| {
let counter = Arc::new(LockFreeCounter::new(0));
let mut handles = vec![];
for _ in 0..4 {
let counter_clone = Arc::clone(&counter);
handles.push(thread::spawn(move || {
for _ in 0..2500 {
counter_clone.increment();
}
}));
}
for handle in handles {
handle.join().unwrap();
}
black_box(counter.get())
});
});
// Stats collector benchmark
group.bench_function("lockfree_stats", |bench| {
let stats = LockFreeStats::new();
bench.iter(|| {
for i in 0..1000 {
stats.record_query(i);
}
black_box(stats.snapshot())
});
});
// Object pool benchmark
group.bench_function("object_pool_acquire_release", |bench| {
let pool = ObjectPool::new(10, || Vec::<f32>::with_capacity(1000));
bench.iter(|| {
let mut obj = pool.acquire();
for i in 0..100 {
obj.push(i as f32);
}
black_box(&*obj);
});
});
group.finish();
}
// Benchmark thread scaling
fn bench_thread_scaling(c: &mut Criterion) {
let mut group = c.benchmark_group("thread_scaling");
let dimensions = 384;
let num_vectors = 10000;
let query: Vec<f32> = (0..dimensions).map(|i| i as f32 * 0.01).collect();
let vectors: Vec<Vec<f32>> = (0..num_vectors)
.map(|i| (0..dimensions).map(|j| (i * j) as f32 * 0.001).collect())
.collect();
for num_threads in [1, 2, 4, 8].iter() {
group.bench_with_input(
BenchmarkId::new("parallel_distance", num_threads),
num_threads,
|bench, &threads| {
bench.iter(|| {
rayon::ThreadPoolBuilder::new()
.num_threads(threads)
.build()
.unwrap()
.install(|| {
let result = batch_distances(
black_box(&query),
black_box(&vectors),
DistanceMetric::Euclidean,
)
.unwrap();
black_box(result)
})
});
},
);
}
group.finish();
}
criterion_group!(
benches,
bench_simd_comparison,
bench_cache_optimization,
bench_arena_allocation,
bench_lockfree,
bench_thread_scaling
);
criterion_main!(benches);

View File

@@ -0,0 +1,74 @@
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use ruvector_core::distance::*;
use ruvector_core::types::DistanceMetric;
fn bench_euclidean(c: &mut Criterion) {
let mut group = c.benchmark_group("euclidean_distance");
for size in [128, 384, 768, 1536].iter() {
let a: Vec<f32> = (0..*size).map(|i| i as f32).collect();
let b: Vec<f32> = (0..*size).map(|i| (i + 1) as f32).collect();
group.bench_with_input(BenchmarkId::from_parameter(size), size, |bench, _| {
bench.iter(|| euclidean_distance(black_box(&a), black_box(&b)));
});
}
group.finish();
}
fn bench_cosine(c: &mut Criterion) {
let mut group = c.benchmark_group("cosine_distance");
for size in [128, 384, 768, 1536].iter() {
let a: Vec<f32> = (0..*size).map(|i| i as f32).collect();
let b: Vec<f32> = (0..*size).map(|i| (i + 1) as f32).collect();
group.bench_with_input(BenchmarkId::from_parameter(size), size, |bench, _| {
bench.iter(|| cosine_distance(black_box(&a), black_box(&b)));
});
}
group.finish();
}
fn bench_dot_product(c: &mut Criterion) {
let mut group = c.benchmark_group("dot_product_distance");
for size in [128, 384, 768, 1536].iter() {
let a: Vec<f32> = (0..*size).map(|i| i as f32).collect();
let b: Vec<f32> = (0..*size).map(|i| (i + 1) as f32).collect();
group.bench_with_input(BenchmarkId::from_parameter(size), size, |bench, _| {
bench.iter(|| dot_product_distance(black_box(&a), black_box(&b)));
});
}
group.finish();
}
fn bench_batch_distances(c: &mut Criterion) {
let query: Vec<f32> = (0..384).map(|i| i as f32).collect();
let vectors: Vec<Vec<f32>> = (0..1000)
.map(|_| (0..384).map(|i| (i as f32) * 1.1).collect())
.collect();
c.bench_function("batch_distances_1000x384", |b| {
b.iter(|| {
batch_distances(
black_box(&query),
black_box(&vectors),
DistanceMetric::Cosine,
)
});
});
}
criterion_group!(
benches,
bench_euclidean,
bench_cosine,
bench_dot_product,
bench_batch_distances
);
criterion_main!(benches);

View File

@@ -0,0 +1,56 @@
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use ruvector_core::types::{DbOptions, DistanceMetric, HnswConfig, SearchQuery};
use ruvector_core::{VectorDB, VectorEntry};
fn bench_hnsw_search(c: &mut Criterion) {
let mut group = c.benchmark_group("hnsw_search");
// Create temp database
let temp_dir = tempfile::tempdir().unwrap();
let options = DbOptions {
dimensions: 128,
distance_metric: DistanceMetric::Cosine,
storage_path: temp_dir
.path()
.join("test.db")
.to_string_lossy()
.to_string(),
hnsw_config: Some(HnswConfig::default()),
quantization: None,
};
let db = VectorDB::new(options).unwrap();
// Insert test vectors
let vectors: Vec<VectorEntry> = (0..1000)
.map(|i| VectorEntry {
id: Some(format!("v{}", i)),
vector: (0..128).map(|j| ((i + j) as f32) * 0.1).collect(),
metadata: None,
})
.collect();
db.insert_batch(vectors).unwrap();
// Benchmark search
let query: Vec<f32> = (0..128).map(|i| i as f32).collect();
for k in [1, 10, 100].iter() {
group.bench_with_input(BenchmarkId::from_parameter(k), k, |bench, &k| {
bench.iter(|| {
db.search(SearchQuery {
vector: black_box(query.clone()),
k: black_box(k),
filter: None,
ef_search: None,
})
.unwrap()
});
});
}
group.finish();
}
criterion_group!(benches, bench_hnsw_search);
criterion_main!(benches);

View File

@@ -0,0 +1,77 @@
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use ruvector_core::quantization::*;
fn bench_scalar_quantization(c: &mut Criterion) {
let mut group = c.benchmark_group("scalar_quantization");
for size in [128, 384, 768, 1536].iter() {
let vector: Vec<f32> = (0..*size).map(|i| i as f32 * 0.1).collect();
group.bench_with_input(BenchmarkId::new("encode", size), size, |bench, _| {
bench.iter(|| ScalarQuantized::quantize(black_box(&vector)));
});
let quantized = ScalarQuantized::quantize(&vector);
group.bench_with_input(BenchmarkId::new("decode", size), size, |bench, _| {
bench.iter(|| quantized.reconstruct());
});
let quantized2 = ScalarQuantized::quantize(&vector);
group.bench_with_input(BenchmarkId::new("distance", size), size, |bench, _| {
bench.iter(|| quantized.distance(black_box(&quantized2)));
});
}
group.finish();
}
fn bench_binary_quantization(c: &mut Criterion) {
let mut group = c.benchmark_group("binary_quantization");
for size in [128, 384, 768, 1536].iter() {
let vector: Vec<f32> = (0..*size)
.map(|i| if i % 2 == 0 { 1.0 } else { -1.0 })
.collect();
group.bench_with_input(BenchmarkId::new("encode", size), size, |bench, _| {
bench.iter(|| BinaryQuantized::quantize(black_box(&vector)));
});
let quantized = BinaryQuantized::quantize(&vector);
group.bench_with_input(BenchmarkId::new("decode", size), size, |bench, _| {
bench.iter(|| quantized.reconstruct());
});
let quantized2 = BinaryQuantized::quantize(&vector);
group.bench_with_input(
BenchmarkId::new("hamming_distance", size),
size,
|bench, _| {
bench.iter(|| quantized.distance(black_box(&quantized2)));
},
);
}
group.finish();
}
fn bench_quantization_compression_ratio(c: &mut Criterion) {
let dimensions = 384;
let vector: Vec<f32> = (0..dimensions).map(|i| i as f32 * 0.01).collect();
c.bench_function("scalar_vs_binary_encoding", |b| {
b.iter(|| {
let scalar = ScalarQuantized::quantize(black_box(&vector));
let binary = BinaryQuantized::quantize(black_box(&vector));
(scalar, binary)
});
});
}
criterion_group!(
benches,
bench_scalar_quantization,
bench_binary_quantization,
bench_quantization_compression_ratio
);
criterion_main!(benches);

View File

@@ -0,0 +1,217 @@
//! Real Benchmarks for RuVector Core
//!
//! These are ACTUAL performance measurements, not simulations.
//! Run with: cargo bench -p ruvector-core --bench real_benchmark
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use ruvector_core::types::{DbOptions, HnswConfig};
use ruvector_core::{DistanceMetric, SearchQuery, VectorDB, VectorEntry};
use tempfile::tempdir;
/// Generate random vectors for benchmarking
fn generate_vectors(count: usize, dim: usize) -> Vec<Vec<f32>> {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
(0..count)
.map(|i| {
(0..dim)
.map(|j| {
let mut hasher = DefaultHasher::new();
(i * dim + j).hash(&mut hasher);
let h = hasher.finish();
((h % 2000) as f32 / 1000.0) - 1.0 // Range [-1, 1]
})
.collect()
})
.collect()
}
/// Benchmark: Vector insertion (single)
fn bench_insert_single(c: &mut Criterion) {
let mut group = c.benchmark_group("insert_single");
for dim in [64, 128, 256, 512].iter() {
let vectors = generate_vectors(1000, *dim);
group.throughput(Throughput::Elements(1));
group.bench_with_input(BenchmarkId::new("dimensions", dim), dim, |b, &dim| {
let dir = tempdir().unwrap();
let options = DbOptions {
storage_path: dir.path().join("bench.db").to_string_lossy().to_string(),
dimensions: dim,
distance_metric: DistanceMetric::Cosine,
hnsw_config: Some(HnswConfig::default()),
quantization: None,
};
let db = VectorDB::new(options).unwrap();
let mut idx = 0;
b.iter(|| {
let entry = VectorEntry {
id: None,
vector: vectors[idx % vectors.len()].clone(),
metadata: None,
};
let _ = black_box(db.insert(entry));
idx += 1;
});
});
}
group.finish();
}
/// Benchmark: Vector insertion (batch)
fn bench_insert_batch(c: &mut Criterion) {
let mut group = c.benchmark_group("insert_batch");
for batch_size in [100, 500, 1000].iter() {
let vectors = generate_vectors(*batch_size, 128);
group.throughput(Throughput::Elements(*batch_size as u64));
group.bench_with_input(
BenchmarkId::new("batch_size", batch_size),
batch_size,
|b, &batch_size| {
b.iter(|| {
let dir = tempdir().unwrap();
let options = DbOptions {
storage_path: dir.path().join("bench.db").to_string_lossy().to_string(),
dimensions: 128,
distance_metric: DistanceMetric::Cosine,
hnsw_config: Some(HnswConfig::default()),
quantization: None,
};
let db = VectorDB::new(options).unwrap();
let entries: Vec<VectorEntry> = vectors
.iter()
.map(|v| VectorEntry {
id: None,
vector: v.clone(),
metadata: None,
})
.collect();
black_box(db.insert_batch(entries).unwrap())
});
},
);
}
group.finish();
}
/// Benchmark: Search (k-NN)
fn bench_search(c: &mut Criterion) {
let mut group = c.benchmark_group("search");
// Pre-populate database
let dir = tempdir().unwrap();
let options = DbOptions {
storage_path: dir.path().join("bench.db").to_string_lossy().to_string(),
dimensions: 128,
distance_metric: DistanceMetric::Cosine,
hnsw_config: Some(HnswConfig {
m: 16,
ef_construction: 100,
ef_search: 50,
max_elements: 100000,
}),
quantization: None,
};
let db = VectorDB::new(options).unwrap();
// Insert 10k vectors
let vectors = generate_vectors(10000, 128);
let entries: Vec<VectorEntry> = vectors
.iter()
.map(|v| VectorEntry {
id: None,
vector: v.clone(),
metadata: None,
})
.collect();
db.insert_batch(entries).unwrap();
// Generate query vectors
let queries = generate_vectors(100, 128);
for k in [10, 50, 100].iter() {
group.throughput(Throughput::Elements(1));
group.bench_with_input(BenchmarkId::new("top_k", k), k, |b, &k| {
let mut query_idx = 0;
b.iter(|| {
let query = &queries[query_idx % queries.len()];
let search_query = SearchQuery {
vector: query.clone(),
k,
filter: None,
ef_search: None,
};
let results = black_box(db.search(search_query));
query_idx += 1;
results
});
});
}
group.finish();
}
/// Benchmark: Distance computation (raw)
fn bench_distance(c: &mut Criterion) {
use ruvector_core::distance::{cosine_distance, dot_product_distance, euclidean_distance};
let mut group = c.benchmark_group("distance");
for dim in [64, 128, 256, 512, 1024].iter() {
let v1: Vec<f32> = (0..*dim).map(|i| (i as f32 * 0.01).sin()).collect();
let v2: Vec<f32> = (0..*dim).map(|i| (i as f32 * 0.02).cos()).collect();
group.throughput(Throughput::Elements(1));
group.bench_with_input(BenchmarkId::new("cosine", dim), dim, |b, _| {
b.iter(|| black_box(cosine_distance(&v1, &v2)));
});
group.bench_with_input(BenchmarkId::new("euclidean", dim), dim, |b, _| {
b.iter(|| black_box(euclidean_distance(&v1, &v2)));
});
group.bench_with_input(BenchmarkId::new("dot_product", dim), dim, |b, _| {
b.iter(|| black_box(dot_product_distance(&v1, &v2)));
});
}
group.finish();
}
/// Benchmark: Quantization
fn bench_quantization(c: &mut Criterion) {
use ruvector_core::quantization::{QuantizedVector, ScalarQuantized};
let mut group = c.benchmark_group("quantization");
for dim in [128, 256, 512].iter() {
let vector: Vec<f32> = (0..*dim).map(|i| (i as f32 * 0.01).sin()).collect();
group.bench_with_input(BenchmarkId::new("scalar_quantize", dim), dim, |b, _| {
b.iter(|| black_box(ScalarQuantized::quantize(&vector)));
});
let quantized = ScalarQuantized::quantize(&vector);
group.bench_with_input(BenchmarkId::new("scalar_distance", dim), dim, |b, _| {
b.iter(|| black_box(quantized.distance(&quantized)));
});
}
group.finish();
}
criterion_group!(
benches,
bench_distance,
bench_quantization,
bench_insert_single,
bench_insert_batch,
bench_search,
);
criterion_main!(benches);