use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use qntz::simd_ops::{hamming_distance, pack_binary_fast, unpack_binary_fast};
fn make_f32_vec(dim: usize, seed: u64) -> Vec<f32> {
let mut state = seed;
(0..dim)
.map(|_| {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
let bits = (state >> 33) as u32;
(bits as f32 / u32::MAX as f32) * 2.0 - 1.0
})
.collect()
}
fn make_binary_codes(dim: usize, seed: u64) -> Vec<u8> {
let mut state = seed;
(0..dim)
.map(|_| {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
(state >> 63) as u8
})
.collect()
}
fn make_packed(dim: usize, seed: u64) -> Vec<u8> {
let codes = make_binary_codes(dim, seed);
let mut packed = vec![0u8; dim.div_ceil(8)];
pack_binary_fast(&codes, &mut packed).unwrap();
packed
}
fn bench_pack_binary(c: &mut Criterion) {
let mut group = c.benchmark_group("pack_binary_fast");
for &dim in &[128usize, 768] {
let codes = make_binary_codes(dim, 42);
let mut packed = vec![0u8; dim.div_ceil(8)];
group.throughput(Throughput::Elements(dim as u64));
group.bench_with_input(BenchmarkId::from_parameter(dim), &dim, |b, _| {
b.iter(|| {
pack_binary_fast(black_box(&codes), black_box(&mut packed)).unwrap();
});
});
}
group.finish();
}
fn bench_unpack_binary(c: &mut Criterion) {
let mut group = c.benchmark_group("unpack_binary_fast");
for &dim in &[128usize, 768] {
let packed = make_packed(dim, 42);
let mut codes = vec![0u8; dim];
group.throughput(Throughput::Elements(dim as u64));
group.bench_with_input(BenchmarkId::from_parameter(dim), &dim, |b, _| {
b.iter(|| {
unpack_binary_fast(black_box(&packed), black_box(&mut codes), dim).unwrap();
});
});
}
group.finish();
}
fn bench_hamming_distance(c: &mut Criterion) {
let mut group = c.benchmark_group("hamming_distance");
for &dim in &[128usize, 768] {
let a = make_packed(dim, 11);
let b = make_packed(dim, 22);
group.throughput(Throughput::Elements(dim as u64));
group.bench_with_input(BenchmarkId::from_parameter(dim), &dim, |b_bench, _| {
b_bench.iter(|| hamming_distance(black_box(&a), black_box(&b)));
});
}
group.finish();
}
#[cfg(feature = "rabitq")]
fn bench_rabitq(c: &mut Criterion) {
use qntz::rabitq::{RaBitQConfig, RaBitQQuantizer};
let mut group = c.benchmark_group("rabitq");
for &dim in &[128usize, 768] {
let q1 = RaBitQQuantizer::with_config(dim, 42, RaBitQConfig::binary()).unwrap();
let doc = make_f32_vec(dim, 1);
let query = make_f32_vec(dim, 2);
let qv1 = q1.quantize(&doc).unwrap();
let rotated_query1 = q1.rotate_query(&query).unwrap();
group.throughput(Throughput::Elements(dim as u64));
group.bench_with_input(BenchmarkId::new("quantize_1bit", dim), &dim, |b, _| {
b.iter(|| q1.quantize(black_box(&doc)).unwrap());
});
group.bench_with_input(
BenchmarkId::new("approximate_l2_sqr_1bit", dim),
&dim,
|b, _| {
b.iter(|| {
q1.approximate_l2_sqr(black_box(&query), black_box(&qv1))
.unwrap()
});
},
);
group.bench_with_input(
BenchmarkId::new("l2_sqr_prerotated_1bit", dim),
&dim,
|b, _| {
b.iter(|| {
RaBitQQuantizer::approximate_l2_sqr_prerotated(
black_box(&rotated_query1),
black_box(&qv1),
)
});
},
);
let q4 = RaBitQQuantizer::with_config(dim, 42, RaBitQConfig::bits4()).unwrap();
let qv4 = q4.quantize(&doc).unwrap();
let rotated_query4 = q4.rotate_query(&query).unwrap();
group.bench_with_input(BenchmarkId::new("quantize_4bit", dim), &dim, |b, _| {
b.iter(|| q4.quantize(black_box(&doc)).unwrap());
});
group.bench_with_input(
BenchmarkId::new("l2_sqr_prerotated_4bit", dim),
&dim,
|b, _| {
b.iter(|| {
RaBitQQuantizer::approximate_l2_sqr_prerotated(
black_box(&rotated_query4),
black_box(&qv4),
)
});
},
);
}
group.finish();
}
#[cfg(feature = "ternary")]
fn bench_ternary(c: &mut Criterion) {
use qntz::ternary::{TernaryConfig, TernaryQuantizer};
let mut group = c.benchmark_group("ternary_quantize");
for &dim in &[128usize, 768] {
let config = TernaryConfig {
threshold_high: 0.3,
threshold_low: -0.3,
normalize: true,
target_sparsity: None,
};
let q = TernaryQuantizer::new(dim, config);
let v = make_f32_vec(dim, 7);
group.throughput(Throughput::Elements(dim as u64));
group.bench_with_input(BenchmarkId::from_parameter(dim), &dim, |b, _| {
b.iter(|| q.quantize(black_box(&v)).unwrap());
});
}
group.finish();
}
#[cfg(feature = "binary")]
fn bench_binary_quantizer(c: &mut Criterion) {
use qntz::binary::BinaryQuantizer;
let dim = 128;
let q = BinaryQuantizer::new(dim, dim, 42);
let v = make_f32_vec(dim, 5);
let code = q.quantize(&v).unwrap();
let mut group = c.benchmark_group("binary_quantizer");
group.throughput(Throughput::Elements(dim as u64));
group.bench_function("quantize_dim128", |b| {
b.iter(|| q.quantize(black_box(&v)).unwrap());
});
group.bench_function("asymmetric_distance_dim128", |b| {
b.iter(|| {
q.asymmetric_distance(black_box(&v), black_box(&code))
.unwrap()
});
});
group.finish();
}
#[cfg(feature = "adaptive")]
fn bench_adaptive(c: &mut Criterion) {
use qntz::adaptive::AdaptiveQuantizer;
let dim = 128;
let n = 64;
let q = AdaptiveQuantizer::new(8).unwrap();
let vectors: Vec<Vec<f32>> = (0..n).map(|i| make_f32_vec(dim, i as u64)).collect();
let query = make_f32_vec(dim, 999);
let packed = q.quantize_packed(&vectors).unwrap();
let mut group = c.benchmark_group("adaptive_quantizer");
group.throughput(Throughput::Elements((dim * n) as u64));
group.bench_function("quantize_packed_64x128", |b| {
b.iter(|| q.quantize_packed(black_box(&vectors)).unwrap());
});
group.bench_function("asymmetric_distances_64x128", |b| {
b.iter(|| packed.asymmetric_distances(black_box(&query)).unwrap());
});
group.finish();
}
fn base_benches(c: &mut Criterion) {
bench_pack_binary(c);
bench_unpack_binary(c);
bench_hamming_distance(c);
#[cfg(feature = "rabitq")]
bench_rabitq(c);
#[cfg(feature = "ternary")]
bench_ternary(c);
#[cfg(feature = "binary")]
bench_binary_quantizer(c);
#[cfg(feature = "adaptive")]
bench_adaptive(c);
}
criterion_group!(benches, base_benches);
criterion_main!(benches);