mod common;
use std::sync::Arc;
use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use tokio::runtime::Runtime;
use common::{DEFAULT_SEED, SAMPLE_SIZE_SLOW, lcg_vec_unit, select_storage};
use laurus::analysis::analyzer::analyzer::Analyzer;
use laurus::analysis::analyzer::standard::StandardAnalyzer;
use laurus::storage::Storage;
use laurus::vector::core::distance::DistanceMetric;
use laurus::vector::core::field::HnswOption;
use laurus::vector::core::vector::Vector;
use laurus::vector::index::ManagedVectorIndex;
use laurus::vector::index::config::{
FlatIndexConfig, HnswIndexConfig, IvfIndexConfig, VectorIndexTypeConfig,
};
use laurus::{Document, Engine, Result, Schema};
const DIM: usize = 128;
fn generate_vectors(count: usize) -> Vec<(u64, String, Vector)> {
let mut state = DEFAULT_SEED;
(0..count)
.map(|i| {
(
i as u64,
"field".to_string(),
Vector::new(lcg_vec_unit(&mut state, DIM)),
)
})
.collect()
}
fn create_storage() -> Arc<dyn Storage> {
select_storage()
}
fn ingest_corpus_sizes() -> Vec<usize> {
let mut sizes = vec![1000usize, 5000];
if std::env::var("LAURUS_BENCH_LARGE").is_ok() {
sizes.push(50_000);
}
sizes
}
fn engine_corpus_sizes() -> Vec<usize> {
let mut sizes = vec![1000usize, 10_000];
if std::env::var("LAURUS_BENCH_LARGE").is_ok() {
sizes.push(50_000);
}
sizes
}
fn flat_type_config() -> VectorIndexTypeConfig {
VectorIndexTypeConfig::Flat(FlatIndexConfig {
dimension: DIM,
distance_metric: DistanceMetric::Cosine,
..Default::default()
})
}
fn ivf_type_config() -> VectorIndexTypeConfig {
VectorIndexTypeConfig::IVF(IvfIndexConfig {
dimension: DIM,
distance_metric: DistanceMetric::Cosine,
n_clusters: 10,
n_probe: 3,
..Default::default()
})
}
fn hnsw_type_config() -> VectorIndexTypeConfig {
VectorIndexTypeConfig::HNSW(HnswIndexConfig {
dimension: DIM,
m: 16,
ef_construction: 200,
distance_metric: DistanceMetric::Cosine,
..Default::default()
})
}
fn fresh_index(index_type: &VectorIndexTypeConfig, name: &'static str) -> ManagedVectorIndex {
let storage = create_storage();
ManagedVectorIndex::new(index_type.clone(), storage, name).unwrap()
}
fn bench_add_vectors(c: &mut Criterion) {
let mut group = c.benchmark_group("vector_ingest/add_vectors");
group.sample_size(SAMPLE_SIZE_SLOW);
for &n in &ingest_corpus_sizes() {
let vectors = generate_vectors(n);
for (label, type_config_factory) in [
("flat", flat_type_config as fn() -> VectorIndexTypeConfig),
("ivf", ivf_type_config as fn() -> VectorIndexTypeConfig),
("hnsw", hnsw_type_config as fn() -> VectorIndexTypeConfig),
] {
{
let mut index = fresh_index(&type_config_factory(), "vec_idx_probe");
index
.add_vectors(vectors.clone())
.expect("add_vectors probe must not error");
}
group.throughput(Throughput::Elements(n as u64));
group.bench_with_input(BenchmarkId::new(label, n), &n, |b, _| {
let vectors = vectors.clone();
b.iter_batched(
|| {
let index = fresh_index(&type_config_factory(), "vec_idx_add");
(index, vectors.clone())
},
|(mut index, vectors)| {
index.add_vectors(vectors).unwrap();
},
BatchSize::SmallInput,
);
});
}
}
group.finish();
}
fn bench_finalize(c: &mut Criterion) {
let mut group = c.benchmark_group("vector_ingest/finalize");
group.sample_size(SAMPLE_SIZE_SLOW);
for &n in &[1000usize, 5000] {
let vectors = generate_vectors(n);
for (label, type_config_factory) in [
("flat", flat_type_config as fn() -> VectorIndexTypeConfig),
("ivf", ivf_type_config as fn() -> VectorIndexTypeConfig),
("hnsw", hnsw_type_config as fn() -> VectorIndexTypeConfig),
] {
group.throughput(Throughput::Elements(n as u64));
group.bench_with_input(BenchmarkId::new(label, n), &n, |b, _| {
let vectors = vectors.clone();
b.iter_batched(
|| {
let mut index = fresh_index(&type_config_factory(), "vec_idx_fin");
index.add_vectors(vectors.clone()).unwrap();
index
},
|mut index| {
index.finalize().unwrap();
},
BatchSize::SmallInput,
);
});
}
}
group.finish();
}
fn bench_bulk_index(c: &mut Criterion) {
let mut group = c.benchmark_group("vector_ingest/bulk");
group.sample_size(SAMPLE_SIZE_SLOW);
for &n in &ingest_corpus_sizes() {
let vectors = generate_vectors(n);
for (label, type_config_factory) in [
("flat", flat_type_config as fn() -> VectorIndexTypeConfig),
("ivf", ivf_type_config as fn() -> VectorIndexTypeConfig),
("hnsw", hnsw_type_config as fn() -> VectorIndexTypeConfig),
] {
group.throughput(Throughput::Elements(n as u64));
group.bench_with_input(BenchmarkId::new(label, n), &n, |b, _| {
let vectors = vectors.clone();
b.iter_batched(
|| {
let index = fresh_index(&type_config_factory(), "vec_idx_bulk");
(index, vectors.clone())
},
|(mut index, vectors)| {
index.add_vectors(vectors).unwrap();
index.finalize().unwrap();
},
BatchSize::SmallInput,
);
});
}
}
group.finish();
}
async fn build_vector_engine() -> Result<Engine> {
let storage = create_storage();
let analyzer: Arc<dyn Analyzer> = Arc::new(StandardAnalyzer::default());
let schema = Schema::builder()
.add_hnsw_field(
"embedding",
HnswOption::new(DIM).distance(DistanceMetric::Cosine),
)
.build();
Engine::builder(storage, schema)
.analyzer(analyzer)
.build()
.await
}
fn build_engine_vector_documents(n: usize) -> Vec<(String, Document)> {
let mut state = DEFAULT_SEED;
(0..n)
.map(|i| {
let vec = lcg_vec_unit(&mut state, DIM);
let doc = Document::builder().add_vector("embedding", vec).build();
(i.to_string(), doc)
})
.collect()
}
fn bench_engine_add_document(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let mut group = c.benchmark_group("vector_ingest/engine_add_document");
group.sample_size(SAMPLE_SIZE_SLOW);
for &n in &engine_corpus_sizes() {
{
let engine = rt.block_on(build_vector_engine()).unwrap();
let docs = build_engine_vector_documents(1);
rt.block_on(async {
for (id, doc) in docs {
engine.add_document(&id, doc).await.unwrap();
}
});
}
group.throughput(Throughput::Elements(n as u64));
group.bench_with_input(BenchmarkId::from_parameter(n), &n, |b, &n| {
b.iter_batched(
|| {
let engine = rt.block_on(build_vector_engine()).unwrap();
let docs = build_engine_vector_documents(n);
(engine, docs)
},
|(engine, docs)| {
rt.block_on(async {
for (id, doc) in docs {
engine.add_document(&id, doc).await.unwrap();
}
});
},
BatchSize::SmallInput,
);
});
}
group.finish();
}
criterion_group!(
benches,
bench_add_vectors,
bench_finalize,
bench_bulk_index,
bench_engine_add_document,
);
criterion_main!(benches);