use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use std::time::Duration;
use tokio::runtime::Runtime;
use uuid::Uuid;
fn create_runtime() -> Runtime {
Runtime::new().unwrap()
}
fn generate_embeddings(count: usize, vector_size: usize) -> Vec<(Uuid, Vec<f32>)> {
(0..count)
.map(|i| {
let id = Uuid::new_v4();
let embedding = (0..vector_size)
.map(|j| ((i + j) as f32 * 0.1).sin())
.collect();
(id, embedding)
})
.collect()
}
fn generate_query_vectors(count: usize, vector_size: usize) -> Vec<Vec<f32>> {
(0..count)
.map(|i| {
(0..vector_size)
.map(|j| ((i + j) as f32 * 0.1).cos())
.collect()
})
.collect()
}
fn bench_batch_upsert(c: &mut Criterion) {
let rt = create_runtime();
let mut group = c.benchmark_group("batch_upsert");
group.measurement_time(Duration::from_secs(10));
for batch_size in [10, 50, 100, 500, 1000].iter() {
group.throughput(Throughput::Elements(*batch_size as u64));
group.bench_with_input(
BenchmarkId::from_parameter(batch_size),
batch_size,
|b, &size| {
b.to_async(&rt).iter(|| async move {
let embeddings = generate_embeddings(size, 768);
tokio::time::sleep(Duration::from_micros(100 * size as u64)).await;
black_box(embeddings)
});
},
);
}
group.finish();
}
fn bench_query_cache(c: &mut Criterion) {
let rt = create_runtime();
let mut group = c.benchmark_group("query_cache");
group.measurement_time(Duration::from_secs(10));
group.bench_function("cache_hit", |b| {
b.to_async(&rt).iter(|| async move {
let query_vector = generate_query_vectors(1, 768).pop().unwrap();
tokio::time::sleep(Duration::from_micros(10)).await;
black_box(query_vector)
});
});
group.bench_function("cache_miss", |b| {
b.to_async(&rt).iter(|| async move {
let query_vector = generate_query_vectors(1, 768).pop().unwrap();
tokio::time::sleep(Duration::from_millis(50)).await;
black_box(query_vector)
});
});
group.finish();
}
fn bench_parallel_batching(c: &mut Criterion) {
let rt = create_runtime();
let mut group = c.benchmark_group("parallel_batching");
group.measurement_time(Duration::from_secs(15));
let total_embeddings = 1000;
let batch_size = 100;
group.bench_function("sequential", |b| {
b.to_async(&rt).iter(|| async move {
let embeddings = generate_embeddings(total_embeddings, 768);
let batches: Vec<_> = embeddings.chunks(batch_size).collect();
for batch in batches {
tokio::time::sleep(Duration::from_millis(10)).await;
black_box(batch);
}
});
});
group.bench_function("parallel", |b| {
b.to_async(&rt).iter(|| async move {
let embeddings = generate_embeddings(total_embeddings, 768);
let batches: Vec<_> = embeddings.chunks(batch_size).collect();
let futures: Vec<_> = batches
.into_iter()
.map(|batch| async move {
tokio::time::sleep(Duration::from_millis(10)).await;
black_box(batch);
})
.collect();
futures::future::join_all(futures).await;
});
});
group.finish();
}
fn bench_cache_warming(c: &mut Criterion) {
let rt = create_runtime();
let mut group = c.benchmark_group("cache_warming");
group.measurement_time(Duration::from_secs(10));
for query_count in [10, 50, 100].iter() {
group.bench_with_input(
BenchmarkId::from_parameter(query_count),
query_count,
|b, &count| {
b.to_async(&rt).iter(|| async move {
let queries = generate_query_vectors(count, 768);
for query in queries {
tokio::time::sleep(Duration::from_micros(50)).await;
black_box(query);
}
});
},
);
}
group.finish();
}
fn bench_vector_similarity(c: &mut Criterion) {
let mut group = c.benchmark_group("vector_similarity");
for vector_size in [384, 768, 1536].iter() {
group.bench_with_input(
BenchmarkId::from_parameter(vector_size),
vector_size,
|b, &size| {
let v1: Vec<f32> = (0..size).map(|i| (i as f32 * 0.1).sin()).collect();
let v2: Vec<f32> = (0..size).map(|i| (i as f32 * 0.1).cos()).collect();
b.iter(|| {
let dot_product: f32 = v1.iter().zip(v2.iter()).map(|(a, b)| a * b).sum();
let magnitude1: f32 = v1.iter().map(|x| x * x).sum::<f32>().sqrt();
let magnitude2: f32 = v2.iter().map(|x| x * x).sum::<f32>().sqrt();
black_box(dot_product / (magnitude1 * magnitude2))
});
},
);
}
group.finish();
}
fn bench_cache_key_generation(c: &mut Criterion) {
let mut group = c.benchmark_group("cache_key_generation");
for vector_size in [384, 768, 1536].iter() {
group.bench_with_input(
BenchmarkId::from_parameter(vector_size),
vector_size,
|b, &size| {
let vector: Vec<f32> = (0..size).map(|i| (i as f32 * 0.1).sin()).collect();
b.iter(|| {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
vector.iter().take(8).for_each(|&f| {
f.to_bits().hash(&mut hasher);
});
black_box(hasher.finish())
});
},
);
}
group.finish();
}
fn bench_lru_operations(c: &mut Criterion) {
use std::collections::HashMap;
let mut group = c.benchmark_group("lru_operations");
group.bench_function("insert", |b| {
b.iter(|| {
let mut cache: HashMap<u64, Vec<f32>> = HashMap::new();
for i in 0..1000 {
let vector = vec![i as f32; 768];
cache.insert(i, vector);
}
black_box(cache)
});
});
group.bench_function("lookup", |b| {
let mut cache: HashMap<u64, Vec<f32>> = HashMap::new();
for i in 0..1000 {
cache.insert(i, vec![i as f32; 768]);
}
b.iter(|| {
for i in 0..100 {
black_box(cache.get(&i));
}
});
});
group.finish();
}
fn bench_filter_construction(c: &mut Criterion) {
let mut group = c.benchmark_group("filter_construction");
group.bench_function("simple_filter", |b| {
b.iter(|| {
let _filter = format!(
r#"{{"must": [{{"key": "chunk_id", "match": {{"value": "{}"}}}}]}}"#,
Uuid::new_v4()
);
black_box(_filter)
});
});
group.bench_function("complex_filter", |b| {
b.iter(|| {
let _filter = format!(
r#"{{"must": [{{"key": "chunk_id", "match": {{"value": "{}"}}}}, {{"key": "document_type", "match": {{"value": "paper"}}}}], "should": [{{"key": "score", "range": {{"gte": 0.8}}}}]}}"#,
Uuid::new_v4()
);
black_box(_filter)
});
});
group.finish();
}
criterion_group!(
benches,
bench_batch_upsert,
bench_query_cache,
bench_parallel_batching,
bench_cache_warming,
bench_vector_similarity,
bench_cache_key_generation,
bench_lru_operations,
bench_filter_construction,
);
criterion_main!(benches);