mirror of
https://github.com/qdrant/qdrant.git
synced 2026-08-06 01:50:57 -05:00
* Prepare measurement of index creation + Remove vector deletion measurement * add hw_counter to add_point functions * Adjust add_point(..) function signatures * Add new measurement type: payload index IO write * Measure payload index IO writes * Some Hw measurement performance improvements * Review remarks * Fix measurements in distributed setups * review fixes --------- Co-authored-by: generall <andrey@vasnetsov.com>
259 lines
8.9 KiB
Rust
259 lines
8.9 KiB
Rust
use std::sync::atomic::AtomicBool;
|
|
|
|
use common::counter::hardware_counter::HardwareCounterCell;
|
|
use common::types::PointOffsetType;
|
|
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
|
|
use dataset::Dataset;
|
|
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
|
|
use itertools::Itertools as _;
|
|
use rand::SeedableRng;
|
|
use rand::rngs::StdRng;
|
|
use segment::fixtures::sparse_fixtures::fixture_sparse_index_from_iter;
|
|
use segment::index::sparse_index::sparse_index_config::{SparseIndexConfig, SparseIndexType};
|
|
use segment::index::sparse_index::sparse_vector_index::{
|
|
SparseVectorIndex, SparseVectorIndexOpenArgs,
|
|
};
|
|
use segment::index::{PayloadIndex, VectorIndex};
|
|
use segment::payload_json;
|
|
use segment::types::PayloadSchemaType::Keyword;
|
|
use segment::types::{Condition, FieldCondition, Filter};
|
|
use sparse::common::sparse_vector::SparseVector;
|
|
use sparse::common::sparse_vector_fixture::{random_positive_sparse_vector, random_sparse_vector};
|
|
use sparse::index::inverted_index::inverted_index_compressed_mmap::InvertedIndexCompressedMmap;
|
|
use sparse::index::inverted_index::inverted_index_ram::InvertedIndexRam;
|
|
use sparse::index::loaders::Csr;
|
|
use tempfile::Builder;
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
mod prof;
|
|
|
|
const NUM_VECTORS: usize = 50_000;
|
|
const MAX_SPARSE_DIM: usize = 30_000;
|
|
const NUM_QUERIES: usize = 2048;
|
|
const TOP: usize = 10;
|
|
const FULL_SCAN_THRESHOLD: usize = 1; // low value to trigger index usage by default
|
|
|
|
fn sparse_vector_index_search_benchmark(c: &mut Criterion) {
|
|
let mut rnd = StdRng::seed_from_u64(0);
|
|
let query_vectors = (0..NUM_QUERIES)
|
|
// Positive values to test pruning.
|
|
.map(|_| random_positive_sparse_vector(&mut rnd, MAX_SPARSE_DIM))
|
|
.collect::<Vec<_>>();
|
|
|
|
let mut rnd = StdRng::seed_from_u64(42);
|
|
let random_vectors = (0..NUM_VECTORS).map(|_| random_sparse_vector(&mut rnd, MAX_SPARSE_DIM));
|
|
sparse_vector_index_search_benchmark_impl(c, "random-50k", random_vectors, &query_vectors);
|
|
|
|
let dataset_vectors = Csr::open(Dataset::NeurIps2023_1M.download().unwrap()).unwrap();
|
|
let query_vectors = Csr::open(Dataset::NeurIps2023Queries.download().unwrap())
|
|
.unwrap()
|
|
.iter()
|
|
.map(|v| v.unwrap())
|
|
.collect_vec();
|
|
sparse_vector_index_search_benchmark_impl(
|
|
c,
|
|
"neurips2023-1M",
|
|
dataset_vectors.iter().map(|v| v.unwrap()),
|
|
&query_vectors,
|
|
);
|
|
}
|
|
|
|
fn sparse_vector_index_search_benchmark_impl(
|
|
c: &mut Criterion,
|
|
group: &str,
|
|
vectors: impl ExactSizeIterator<Item = SparseVector>,
|
|
query_vectors: &[SparseVector],
|
|
) {
|
|
let mut group = c.benchmark_group(format!("sparse_vector_index_search/{group}"));
|
|
group.sample_size(10);
|
|
|
|
let vectors_len = vectors.len();
|
|
|
|
let stopped = AtomicBool::new(false);
|
|
|
|
let data_dir = Builder::new().prefix("data_dir").tempdir().unwrap();
|
|
|
|
let sparse_vector_index = fixture_sparse_index_from_iter::<InvertedIndexRam>(
|
|
data_dir.path(),
|
|
progress("Indexing (1/2)", vectors_len).wrap_iter(vectors),
|
|
FULL_SCAN_THRESHOLD,
|
|
SparseIndexType::MutableRam,
|
|
)
|
|
.unwrap();
|
|
|
|
// adding payload on field
|
|
let field_name = "field";
|
|
let field_value = "important value";
|
|
let payload = payload_json! {field_name: field_value};
|
|
|
|
let hw_counter = HardwareCounterCell::new();
|
|
|
|
// all points have the same payload
|
|
let mut payload_index = sparse_vector_index.payload_index().borrow_mut();
|
|
for idx in 0..NUM_VECTORS {
|
|
payload_index
|
|
.set_payload(idx as PointOffsetType, &payload, &None, &hw_counter)
|
|
.unwrap();
|
|
}
|
|
drop(payload_index);
|
|
|
|
let mut query_vector_it = query_vectors.iter().cycle();
|
|
|
|
// mmap inverted index
|
|
let mmap_index_dir = Builder::new().prefix("mmap_index_dir").tempdir().unwrap();
|
|
let sparse_index_config =
|
|
SparseIndexConfig::new(Some(FULL_SCAN_THRESHOLD), SparseIndexType::Mmap, None);
|
|
let pb = progress("Indexing (2/2)", vectors_len);
|
|
let sparse_vector_index_mmap: SparseVectorIndex<InvertedIndexCompressedMmap<f32>> =
|
|
SparseVectorIndex::open(SparseVectorIndexOpenArgs {
|
|
config: sparse_index_config,
|
|
id_tracker: sparse_vector_index.id_tracker().clone(),
|
|
vector_storage: sparse_vector_index.vector_storage().clone(),
|
|
payload_index: sparse_vector_index.payload_index().clone(),
|
|
path: mmap_index_dir.path(),
|
|
stopped: &stopped,
|
|
tick_progress: || pb.inc(1),
|
|
})
|
|
.unwrap();
|
|
pb.finish_and_clear();
|
|
assert_eq!(sparse_vector_index_mmap.indexed_vector_count(), vectors_len);
|
|
|
|
// intent: bench `search` without filter on mmap inverted index
|
|
group.bench_function("mmap-inverted-index-search", |b| {
|
|
b.iter_batched(
|
|
|| query_vector_it.next().unwrap().clone().into(),
|
|
|vec| {
|
|
let results = sparse_vector_index_mmap
|
|
.search(&[&vec], None, TOP, None, &Default::default())
|
|
.unwrap();
|
|
|
|
assert_eq!(results[0].len(), TOP);
|
|
},
|
|
BatchSize::SmallInput,
|
|
)
|
|
});
|
|
|
|
// intent: bench `search` without filter
|
|
group.bench_function("inverted-index-search", |b| {
|
|
b.iter_batched(
|
|
|| query_vector_it.next().unwrap().clone().into(),
|
|
|vec| {
|
|
let results = sparse_vector_index
|
|
.search(&[&vec], None, TOP, None, &Default::default())
|
|
.unwrap();
|
|
|
|
assert_eq!(results[0].len(), TOP);
|
|
},
|
|
BatchSize::SmallInput,
|
|
)
|
|
});
|
|
|
|
// filter by field
|
|
let filter = Filter::new_must(Condition::Field(FieldCondition::new_match(
|
|
field_name.parse().unwrap(),
|
|
field_value.to_owned().into(),
|
|
)));
|
|
|
|
// intent: bench plain search when the filtered payload key is not indexed
|
|
if vectors_len < 100_000 {
|
|
group.bench_function("inverted-index-filtered-plain", |b| {
|
|
b.iter_batched(
|
|
|| query_vector_it.next().unwrap(),
|
|
|vec| {
|
|
let mut prefiltered_points = None;
|
|
let results = sparse_vector_index
|
|
.search_plain(
|
|
vec,
|
|
&filter,
|
|
TOP,
|
|
&mut prefiltered_points,
|
|
&Default::default(),
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(results.len(), TOP);
|
|
},
|
|
BatchSize::SmallInput,
|
|
)
|
|
});
|
|
}
|
|
|
|
let mut payload_index = sparse_vector_index.payload_index().borrow_mut();
|
|
|
|
// create payload field index
|
|
payload_index
|
|
.set_indexed(&field_name.parse().unwrap(), Keyword, &hw_counter)
|
|
.unwrap();
|
|
|
|
drop(payload_index);
|
|
|
|
// intent: bench `search` when the filtered payload key is indexed
|
|
group.bench_function("inverted-index-filtered-payload-index", |b| {
|
|
b.iter_batched(
|
|
|| query_vector_it.next().unwrap().clone().into(),
|
|
|vec| {
|
|
let results = sparse_vector_index
|
|
.search(&[&vec], Some(&filter), TOP, None, &Default::default())
|
|
.unwrap();
|
|
|
|
assert_eq!(results[0].len(), TOP);
|
|
},
|
|
BatchSize::SmallInput,
|
|
);
|
|
});
|
|
|
|
// intent: bench plain search when the filtered payload key is indexed
|
|
if vectors_len < 100_000 {
|
|
group.bench_function("plain-filtered-payload-index", |b| {
|
|
b.iter_batched(
|
|
|| query_vector_it.next().unwrap(),
|
|
|vec| {
|
|
let mut prefiltered_points = None;
|
|
let results = sparse_vector_index
|
|
.search_plain(
|
|
vec,
|
|
&filter,
|
|
TOP,
|
|
&mut prefiltered_points,
|
|
&Default::default(),
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(results.len(), TOP);
|
|
},
|
|
BatchSize::SmallInput,
|
|
)
|
|
});
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
fn progress(name: &str, length: usize) -> ProgressBar {
|
|
let pb =
|
|
ProgressBar::with_draw_target(Some(length as u64), ProgressDrawTarget::stderr_with_hz(12));
|
|
pb.set_style(
|
|
ProgressStyle::default_bar()
|
|
.template("{msg} {wide_bar} {pos}/{len} (eta:{eta})")
|
|
.unwrap(),
|
|
);
|
|
pb.set_message(name.to_owned());
|
|
pb
|
|
}
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
criterion_group! {
|
|
name = benches;
|
|
config = Criterion::default().with_profiler(prof::FlamegraphProfiler::new(100));
|
|
targets = sparse_vector_index_search_benchmark
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
criterion_group! {
|
|
name = benches;
|
|
config = Criterion::default();
|
|
targets = sparse_vector_index_search_benchmark,
|
|
}
|
|
|
|
criterion_main!(benches);
|