use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use std::hint::black_box;
use yo_common::Rng;
use yo_vector::{Bits, Coded, Quantizer};
fn corpus(dim: usize, n: usize) -> Vec<Vec<f32>> {
let mut rng = Rng::new(0x5eed);
(0..n)
.map(|_| {
let mut v: Vec<f32> = (0..dim)
.map(|_| (rng.next_u64() >> 40) as f32 / (1u32 << 24) as f32 - 0.5)
.collect();
let len = v.iter().map(|c| c * c).sum::<f32>().sqrt();
for c in &mut v {
*c /= len;
}
v
})
.collect()
}
fn partition(q: &Quantizer, vs: &[Vec<f32>], centroid: &[f32]) -> (Vec<u8>, Vec<Coded>) {
let width = q.code_bytes();
let mut codes = vec![0u8; width * vs.len()];
let meta = vs
.iter()
.enumerate()
.map(|(i, v)| q.encode(v, centroid, &mut codes[i * width..(i + 1) * width]))
.collect();
(codes, meta)
}
fn bench_encode(c: &mut Criterion) {
let mut g = c.benchmark_group("rabitq/encode");
for dim in [128usize, 256, 768, 1536] {
let vs = corpus(dim, 64);
let centroid = vec![0.0f32; dim];
for (bits, name) in [(Bits::One, "one"), (Bits::Four, "four")] {
let q = Quantizer::new(dim, bits, 7);
let mut code = vec![0u8; q.code_bytes()];
g.bench_with_input(BenchmarkId::new(name, dim), &dim, |b, _| {
let mut i = 0usize;
b.iter(|| {
i = (i + 1) % vs.len();
black_box(q.encode(black_box(&vs[i]), ¢roid, &mut code))
});
});
}
}
g.finish();
}
fn bench_scan(c: &mut Criterion) {
let mut g = c.benchmark_group("rabitq/scan");
let n = 1024;
for dim in [128usize, 256, 768] {
let vs = corpus(dim, n);
let centroid = vec![0.0f32; dim];
let query = corpus(dim, 1).pop().expect("one vector");
for (bits, name) in [(Bits::One, "one"), (Bits::Four, "four")] {
let q = Quantizer::new(dim, bits, 7);
let (codes, meta) = partition(&q, &vs, ¢roid);
let width = q.code_bytes();
let prepared = q.query(&query, ¢roid);
let mut out = vec![0.0f32; n];
g.bench_with_input(BenchmarkId::new(name, dim), &dim, |b, _| {
b.iter(|| {
prepared.scan(&codes, &meta, &mut out);
black_box(out.iter().copied().fold(f32::INFINITY, f32::min))
});
});
g.bench_with_input(
BenchmarkId::new(format!("apiece/{name}"), dim),
&dim,
|b, _| {
b.iter(|| {
let mut best = f32::INFINITY;
for i in 0..n {
let d = prepared.distance(&codes[i * width..(i + 1) * width], &meta[i]);
best = best.min(d);
}
black_box(best)
});
},
);
g.bench_with_input(
BenchmarkId::new(format!("exact/{name}"), dim),
&dim,
|b, _| {
b.iter(|| {
let mut best = f32::INFINITY;
for i in 0..n {
let c =
prepared.cosine_exact(&codes[i * width..(i + 1) * width], &meta[i]);
best = best.min(c);
}
black_box(best)
});
},
);
}
}
g.finish();
}
fn bench_query(c: &mut Criterion) {
let mut g = c.benchmark_group("rabitq/query");
for dim in [128usize, 256, 768] {
let query = corpus(dim, 1).pop().expect("one vector");
let centroid = vec![0.0f32; dim];
let q = Quantizer::new(dim, Bits::One, 7);
g.bench_with_input(BenchmarkId::from_parameter(dim), &dim, |b, _| {
b.iter(|| black_box(q.query(black_box(&query), ¢roid)));
});
let rotated = q.rotate(&query);
g.bench_with_input(BenchmarkId::new("rotated", dim), &dim, |b, _| {
b.iter(|| black_box(q.query_rotated(black_box(&rotated), ¢roid)));
});
}
g.finish();
}
criterion_group!(benches, bench_encode, bench_scan, bench_query);
criterion_main!(benches);