#![allow(missing_docs)]
use std::collections::HashMap;
use std::hint::black_box;
use criterion::{criterion_group, criterion_main, Criterion, Throughput};
use dynvec::distance::Distance;
use dynvec::encoding::Codec;
use dynvec::index::HnswParams;
use dynvec::storage::{IndexAlgorithm, TableSchema, VectorStore};
fn rand_vec(seed: u64, dim: usize) -> Vec<f32> {
let mut x = seed;
let mut v = Vec::with_capacity(dim);
for _ in 0..dim {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
let bits = (x >> 11) & ((1_u64 << 53) - 1);
#[allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
reason = "bench fixture: deterministic PRNG narrowed to f32"
)]
let r = (((bits as f64) / ((1_u64 << 53) as f64)) * 2.0 - 1.0) as f32;
v.push(r);
}
v
}
fn schema(name: &str, dim: u16) -> TableSchema {
TableSchema {
name: name.to_string(),
dim,
codec: Codec::Int8Quantized,
distance: Distance::Euclidean,
hnsw: HnswParams::default(),
algorithm: IndexAlgorithm::Hnsw,
}
}
fn schema_turbovec(name: &str, dim: u16) -> TableSchema {
TableSchema {
name: name.to_string(),
dim,
codec: Codec::Turbovec4Bit,
distance: Distance::Cosine,
hnsw: HnswParams::default(),
algorithm: IndexAlgorithm::Flat,
}
}
fn schema_turbo_hnsw(name: &str, dim: u16, codec: Codec) -> TableSchema {
TableSchema {
name: name.to_string(),
dim,
codec,
distance: Distance::Cosine,
hnsw: HnswParams::default(),
algorithm: IndexAlgorithm::Hnsw,
}
}
fn bench_insert(c: &mut Criterion) {
let mut group = c.benchmark_group("insert");
group.throughput(Throughput::Elements(1));
group.bench_function("upsert_64d", |b| {
let store = VectorStore::in_memory();
store.create_table(schema("t", 64)).unwrap();
let mut i = 0_u64;
b.iter(|| {
let v = rand_vec(i, 64);
let key = format!("k{i}").into_bytes();
store.upsert("t", key, &v, HashMap::new()).unwrap();
i += 1;
black_box(i);
});
});
group.finish();
}
fn bench_search(c: &mut Criterion) {
let store = VectorStore::in_memory();
store.create_table(schema("t", 64)).unwrap();
for i in 0..10_000_u64 {
let v = rand_vec(i.wrapping_mul(0x9E37_79B9_7F4A_7C15), 64);
let key = format!("k{i}").into_bytes();
store.upsert("t", key, &v, HashMap::new()).unwrap();
}
let mut group = c.benchmark_group("search");
group.throughput(Throughput::Elements(1));
group.bench_function("topk10_64d_10k", |b| {
let mut q = 0_u64;
b.iter(|| {
let qv = rand_vec(q.wrapping_mul(0x517C_C1B7_2722_0A95), 64);
let hits = store.search("t", &qv, 10, None).unwrap();
q += 1;
black_box(hits);
});
});
group.finish();
}
fn bench_search_turbovec(c: &mut Criterion) {
let store = VectorStore::in_memory();
store.create_table(schema_turbovec("t", 64)).unwrap();
for i in 0..10_000_u64 {
let v = rand_vec(i.wrapping_mul(0x9E37_79B9_7F4A_7C15), 64);
let key = format!("k{i}").into_bytes();
store.upsert("t", key, &v, HashMap::new()).unwrap();
}
let mut group = c.benchmark_group("search_turbovec_4bit");
group.throughput(Throughput::Elements(1));
group.bench_function("topk10_64d_10k", |b| {
let mut q = 0_u64;
b.iter(|| {
let qv = rand_vec(q.wrapping_mul(0x517C_C1B7_2722_0A95), 64);
let hits = store.search("t", &qv, 10, None).unwrap();
q += 1;
black_box(hits);
});
});
group.finish();
}
fn bench_search_turbo_hnsw_4bit(c: &mut Criterion) {
let store = VectorStore::in_memory();
store
.create_table(schema_turbo_hnsw("t", 64, Codec::Turbovec4Bit))
.unwrap();
for i in 0..10_000_u64 {
let v = rand_vec(i.wrapping_mul(0x9E37_79B9_7F4A_7C15), 64);
let key = format!("k{i}").into_bytes();
store.upsert("t", key, &v, HashMap::new()).unwrap();
}
let mut group = c.benchmark_group("search_turbo_hnsw_4bit");
group.throughput(Throughput::Elements(1));
group.bench_function("topk10_64d_10k", |b| {
let mut q = 0_u64;
b.iter(|| {
let qv = rand_vec(q.wrapping_mul(0x517C_C1B7_2722_0A95), 64);
let hits = store.search("t", &qv, 10, None).unwrap();
q += 1;
black_box(hits);
});
});
group.finish();
}
fn bench_search_turbo_hnsw_2bit(c: &mut Criterion) {
let store = VectorStore::in_memory();
store
.create_table(schema_turbo_hnsw("t", 64, Codec::Turbovec2Bit))
.unwrap();
for i in 0..10_000_u64 {
let v = rand_vec(i.wrapping_mul(0x9E37_79B9_7F4A_7C15), 64);
let key = format!("k{i}").into_bytes();
store.upsert("t", key, &v, HashMap::new()).unwrap();
}
let mut group = c.benchmark_group("search_turbo_hnsw_2bit");
group.throughput(Throughput::Elements(1));
group.bench_function("topk10_64d_10k", |b| {
let mut q = 0_u64;
b.iter(|| {
let qv = rand_vec(q.wrapping_mul(0x517C_C1B7_2722_0A95), 64);
let hits = store.search("t", &qv, 10, None).unwrap();
q += 1;
black_box(hits);
});
});
group.finish();
}
fn bench_search_turbo_hnsw_4bit_100k(c: &mut Criterion) {
let store = VectorStore::in_memory();
store
.create_table(schema_turbo_hnsw("t", 64, Codec::Turbovec4Bit))
.unwrap();
for i in 0..100_000_u64 {
let v = rand_vec(i.wrapping_mul(0x9E37_79B9_7F4A_7C15), 64);
let key = format!("k{i}").into_bytes();
store.upsert("t", key, &v, HashMap::new()).unwrap();
}
let mut group = c.benchmark_group("search_turbo_hnsw_4bit");
group.throughput(Throughput::Elements(1));
group.bench_function("topk10_64d_100k", |b| {
let mut q = 0_u64;
b.iter(|| {
let qv = rand_vec(q.wrapping_mul(0x517C_C1B7_2722_0A95), 64);
let hits = store.search("t", &qv, 10, None).unwrap();
q += 1;
black_box(hits);
});
});
group.finish();
}
fn bench_search_turbo_hnsw_2bit_100k(c: &mut Criterion) {
let store = VectorStore::in_memory();
store
.create_table(schema_turbo_hnsw("t", 64, Codec::Turbovec2Bit))
.unwrap();
for i in 0..100_000_u64 {
let v = rand_vec(i.wrapping_mul(0x9E37_79B9_7F4A_7C15), 64);
let key = format!("k{i}").into_bytes();
store.upsert("t", key, &v, HashMap::new()).unwrap();
}
let mut group = c.benchmark_group("search_turbo_hnsw_2bit");
group.throughput(Throughput::Elements(1));
group.bench_function("topk10_64d_100k", |b| {
let mut q = 0_u64;
b.iter(|| {
let qv = rand_vec(q.wrapping_mul(0x517C_C1B7_2722_0A95), 64);
let hits = store.search("t", &qv, 10, None).unwrap();
q += 1;
black_box(hits);
});
});
group.finish();
}
criterion_group!(
benches,
bench_insert,
bench_search,
bench_search_turbovec,
bench_search_turbo_hnsw_4bit,
bench_search_turbo_hnsw_2bit,
bench_search_turbo_hnsw_4bit_100k,
bench_search_turbo_hnsw_2bit_100k,
);
criterion_main!(benches);