From 02bd1dff37ae98d202b70510f3bda0a48462a2a0 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Thu, 18 Jun 2026 07:16:54 -0700 Subject: [PATCH 1/2] Improve SVS topKQuery() by using "single-search" for non-multi --- src/VecSim/algorithms/svs/svs.h | 57 +++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/src/VecSim/algorithms/svs/svs.h b/src/VecSim/algorithms/svs/svs.h index 7dc15dc0d..d08874940 100644 --- a/src/VecSim/algorithms/svs/svs.h +++ b/src/VecSim/algorithms/svs/svs.h @@ -587,29 +587,58 @@ class SVSIndex : public VecSimIndexAbstract, fl auto processed_query_ptr = this->preprocessQuery(queryBlob); const void *processed_query = processed_query_ptr.get(); - auto query = svs::data::ConstSimpleDataView{ - static_cast(processed_query), 1, this->dim}; - auto result = svs::QueryResult{query.size(), k}; auto sp = svs_details::joinSearchParams(impl_->get_search_parameters(), queryParams, is_two_level_lvq); auto timeoutCtx = queryParams ? queryParams->timeoutCtx : nullptr; auto cancel = [timeoutCtx]() { return VECSIM_TIMEOUT(timeoutCtx); }; - impl_->search(result.view(), query, sp, cancel); - if (cancel()) { - rep->code = VecSim_QueryReply_TimedOut; - return rep; - } + if constexpr (isMulti) { + auto query = svs::data::ConstSimpleDataView{ + static_cast(processed_query), 1, this->dim}; + auto result = svs::QueryResult{query.size(), k}; + impl_->search(result.view(), query, sp, cancel); + if (cancel()) { + rep->code = VecSim_QueryReply_TimedOut; + return rep; + } + + assert(result.n_queries() == 1); - assert(result.n_queries() == 1); + const auto n_neighbors = result.n_neighbors(); + rep->results.reserve(n_neighbors); - const auto n_neighbors = result.n_neighbors(); - rep->results.reserve(n_neighbors); + for (size_t i = 0; i < n_neighbors; i++) { + rep->results.push_back( + VecSimQueryResult{result.index(0, i), toVecSimDistance(result.distance(0, i))}); + } + } else { + // Use the single-query search path of MutableVamanaIndex to avoid the + // parallel batch-search overhead for a single query. + auto scratch = impl_->scratchspace(sp); + // Ensure the scratch buffer can hold at least `k` neighbors. + if (scratch.buffer.target_capacity() < k) { + scratch.buffer.change_maxsize(k); + } - for (size_t i = 0; i < n_neighbors; i++) { - rep->results.push_back( - VecSimQueryResult{result.index(0, i), toVecSimDistance(result.distance(0, i))}); + auto query = std::span{static_cast(processed_query), + this->dim}; + impl_->search(query, scratch, cancel); + if (cancel()) { + rep->code = VecSim_QueryReply_TimedOut; + return rep; + } + + const auto &buffer = scratch.buffer; + const auto n_neighbors = std::min(k, buffer.size()); + rep->results.reserve(n_neighbors); + + for (size_t i = 0; i < n_neighbors; i++) { + const auto neighbor = buffer[i]; + rep->results.push_back( + VecSimQueryResult{impl_->translate_internal_id(neighbor.id()), + toVecSimDistance(neighbor.distance())}); + } } // Workaround for VecSim merge_results() that expects results to be sorted // by score, then by id from both indices. From 70238c348a9cd08723d8c9f4614d93c3add297a5 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Fri, 10 Jul 2026 06:11:22 -0700 Subject: [PATCH 2/2] Small fix --- src/VecSim/algorithms/svs/svs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VecSim/algorithms/svs/svs.h b/src/VecSim/algorithms/svs/svs.h index d08874940..9cf91c17e 100644 --- a/src/VecSim/algorithms/svs/svs.h +++ b/src/VecSim/algorithms/svs/svs.h @@ -634,7 +634,7 @@ class SVSIndex : public VecSimIndexAbstract, fl rep->results.reserve(n_neighbors); for (size_t i = 0; i < n_neighbors; i++) { - const auto neighbor = buffer[i]; + const auto &neighbor = buffer[i]; rep->results.push_back( VecSimQueryResult{impl_->translate_internal_id(neighbor.id()), toVecSimDistance(neighbor.distance())});