From 3e3b1aa444de3bd58f06fe2e2a05d808752df4ca Mon Sep 17 00:00:00 2001 From: advisedy Date: Mon, 20 Jul 2026 08:30:40 +0800 Subject: [PATCH 1/5] perf(search): lazily initialize HNSW random generator --- src/search/hnsw_indexer.cc | 9 ++++++--- src/search/hnsw_indexer.h | 7 +++++-- tests/cppunit/hnsw_index_test.cc | 6 ++++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/search/hnsw_indexer.cc b/src/search/hnsw_indexer.cc index cb9aca7705a..15ce819c6f5 100644 --- a/src/search/hnsw_indexer.cc +++ b/src/search/hnsw_indexer.cc @@ -173,16 +173,19 @@ StatusOr ComputeSimilarity(const VectorItem& left, const VectorItem& rig } HnswIndex::HnswIndex(const SearchKey& search_key, HnswVectorFieldMetadata* vector, engine::Storage* storage, - std::random_device::result_type seed) + std::optional seed) : search_key(search_key), metadata(vector), storage(storage), - generator(std::mt19937(seed)), + seed(seed), m_level_normalization_factor(1.0 / std::log(metadata->m)) {} uint16_t HnswIndex::RandomizeLayer() { + if (!generator) { + generator = std::make_unique(seed.value_or(std::random_device()())); + } std::uniform_real_distribution level_dist(0.0, 1.0); - double r = level_dist(generator); + double r = level_dist(*generator); double log_val = -std::log(r); double layer_val = log_val * m_level_normalization_factor; return static_cast(std::floor(layer_val)); diff --git a/src/search/hnsw_indexer.h b/src/search/hnsw_indexer.h index 579352a8b22..c9f61c1b40f 100644 --- a/src/search/hnsw_indexer.h +++ b/src/search/hnsw_indexer.h @@ -20,6 +20,8 @@ #pragma once +#include +#include #include #include #include @@ -89,11 +91,12 @@ struct HnswIndex { HnswVectorFieldMetadata* metadata; engine::Storage* storage = nullptr; - std::mt19937 generator; + std::optional seed; + std::unique_ptr generator; double m_level_normalization_factor; HnswIndex(const SearchKey& search_key, HnswVectorFieldMetadata* vector, engine::Storage* storage, - std::random_device::result_type seed = std::random_device()()); + std::optional seed = std::nullopt); static StatusOr> DecodeNodesToVectorItems(engine::Context& ctx, const std::vector& node_key, diff --git a/tests/cppunit/hnsw_index_test.cc b/tests/cppunit/hnsw_index_test.cc index 022f2a73880..996e2fb51f2 100644 --- a/tests/cppunit/hnsw_index_test.cc +++ b/tests/cppunit/hnsw_index_test.cc @@ -118,6 +118,12 @@ TEST_F(HnswIndexTest, ComputeSimilarity) { hnsw_index->metadata->distance_metric = redis::DistanceMetric::L2; } +TEST_F(HnswIndexTest, RandomGeneratorIsInitializedLazily) { + EXPECT_FALSE(hnsw_index->generator); + hnsw_index->RandomizeLayer(); + EXPECT_TRUE(hnsw_index->generator); +} + TEST_F(HnswIndexTest, RandomizeLayer) { constexpr size_t kSampleSize = 50000; From 5cfd455dd0b42d9df3cf4e953a8cc75243871d09 Mon Sep 17 00:00:00 2001 From: advisedy Date: Mon, 20 Jul 2026 17:22:17 +0800 Subject: [PATCH 2/5] fix(search): only call random_device when seed is not set Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/search/hnsw_indexer.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search/hnsw_indexer.cc b/src/search/hnsw_indexer.cc index 15ce819c6f5..82d03a9cecf 100644 --- a/src/search/hnsw_indexer.cc +++ b/src/search/hnsw_indexer.cc @@ -182,7 +182,8 @@ HnswIndex::HnswIndex(const SearchKey& search_key, HnswVectorFieldMetadata* vecto uint16_t HnswIndex::RandomizeLayer() { if (!generator) { - generator = std::make_unique(seed.value_or(std::random_device()())); + const auto actual_seed = seed ? *seed : std::random_device()(); + generator = std::make_unique(actual_seed); } std::uniform_real_distribution level_dist(0.0, 1.0); double r = level_dist(*generator); From 3b7a688a51d71bbaafb0c75a11f1fe9635911837 Mon Sep 17 00:00:00 2001 From: advisedy Date: Thu, 23 Jul 2026 21:10:13 +0800 Subject: [PATCH 3/5] perf(search): use a static thread-local RNG for HNSW indexes --- src/search/hnsw_indexer.cc | 8 ++------ src/search/hnsw_indexer.h | 2 -- tests/cppunit/hnsw_index_test.cc | 6 ------ 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/search/hnsw_indexer.cc b/src/search/hnsw_indexer.cc index 82d03a9cecf..8ce688a932d 100644 --- a/src/search/hnsw_indexer.cc +++ b/src/search/hnsw_indexer.cc @@ -24,7 +24,6 @@ #include #include -#include #include #include #include @@ -181,12 +180,9 @@ HnswIndex::HnswIndex(const SearchKey& search_key, HnswVectorFieldMetadata* vecto m_level_normalization_factor(1.0 / std::log(metadata->m)) {} uint16_t HnswIndex::RandomizeLayer() { - if (!generator) { - const auto actual_seed = seed ? *seed : std::random_device()(); - generator = std::make_unique(actual_seed); - } + static thread_local std::mt19937 generator = [this] { return std::mt19937(seed.value_or(std::random_device()())); }(); std::uniform_real_distribution level_dist(0.0, 1.0); - double r = level_dist(*generator); + double r = level_dist(generator); double log_val = -std::log(r); double layer_val = log_val * m_level_normalization_factor; return static_cast(std::floor(layer_val)); diff --git a/src/search/hnsw_indexer.h b/src/search/hnsw_indexer.h index c9f61c1b40f..444a83a150c 100644 --- a/src/search/hnsw_indexer.h +++ b/src/search/hnsw_indexer.h @@ -20,7 +20,6 @@ #pragma once -#include #include #include #include @@ -92,7 +91,6 @@ struct HnswIndex { engine::Storage* storage = nullptr; std::optional seed; - std::unique_ptr generator; double m_level_normalization_factor; HnswIndex(const SearchKey& search_key, HnswVectorFieldMetadata* vector, engine::Storage* storage, diff --git a/tests/cppunit/hnsw_index_test.cc b/tests/cppunit/hnsw_index_test.cc index 996e2fb51f2..022f2a73880 100644 --- a/tests/cppunit/hnsw_index_test.cc +++ b/tests/cppunit/hnsw_index_test.cc @@ -118,12 +118,6 @@ TEST_F(HnswIndexTest, ComputeSimilarity) { hnsw_index->metadata->distance_metric = redis::DistanceMetric::L2; } -TEST_F(HnswIndexTest, RandomGeneratorIsInitializedLazily) { - EXPECT_FALSE(hnsw_index->generator); - hnsw_index->RandomizeLayer(); - EXPECT_TRUE(hnsw_index->generator); -} - TEST_F(HnswIndexTest, RandomizeLayer) { constexpr size_t kSampleSize = 50000; From ff60ae261f04674fc7b3c04fb55a9b653b69a7c1 Mon Sep 17 00:00:00 2001 From: advisedy Date: Fri, 24 Jul 2026 00:48:47 +0800 Subject: [PATCH 4/5] perf(search): drop seed, make the RNG a class member --- src/search/hnsw_indexer.cc | 7 +++---- src/search/hnsw_indexer.h | 7 +++---- tests/cppunit/hnsw_index_test.cc | 3 +-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/search/hnsw_indexer.cc b/src/search/hnsw_indexer.cc index 8ce688a932d..07fb41695dc 100644 --- a/src/search/hnsw_indexer.cc +++ b/src/search/hnsw_indexer.cc @@ -171,16 +171,15 @@ StatusOr ComputeSimilarity(const VectorItem& left, const VectorItem& rig } } -HnswIndex::HnswIndex(const SearchKey& search_key, HnswVectorFieldMetadata* vector, engine::Storage* storage, - std::optional seed) +thread_local std::mt19937 HnswIndex::generator{std::random_device()()}; + +HnswIndex::HnswIndex(const SearchKey& search_key, HnswVectorFieldMetadata* vector, engine::Storage* storage) : search_key(search_key), metadata(vector), storage(storage), - seed(seed), m_level_normalization_factor(1.0 / std::log(metadata->m)) {} uint16_t HnswIndex::RandomizeLayer() { - static thread_local std::mt19937 generator = [this] { return std::mt19937(seed.value_or(std::random_device()())); }(); std::uniform_real_distribution level_dist(0.0, 1.0); double r = level_dist(generator); double log_val = -std::log(r); diff --git a/src/search/hnsw_indexer.h b/src/search/hnsw_indexer.h index 444a83a150c..53d5c6248a1 100644 --- a/src/search/hnsw_indexer.h +++ b/src/search/hnsw_indexer.h @@ -20,7 +20,6 @@ #pragma once -#include #include #include #include @@ -90,11 +89,11 @@ struct HnswIndex { HnswVectorFieldMetadata* metadata; engine::Storage* storage = nullptr; - std::optional seed; double m_level_normalization_factor; - HnswIndex(const SearchKey& search_key, HnswVectorFieldMetadata* vector, engine::Storage* storage, - std::optional seed = std::nullopt); + static thread_local std::mt19937 generator; + + HnswIndex(const SearchKey& search_key, HnswVectorFieldMetadata* vector, engine::Storage* storage); static StatusOr> DecodeNodesToVectorItems(engine::Context& ctx, const std::vector& node_key, diff --git a/tests/cppunit/hnsw_index_test.cc b/tests/cppunit/hnsw_index_test.cc index 022f2a73880..332c1582afc 100644 --- a/tests/cppunit/hnsw_index_test.cc +++ b/tests/cppunit/hnsw_index_test.cc @@ -66,7 +66,6 @@ struct HnswIndexTest : TestBase { std::string idx_name = "hnsw_test_idx"; std::string key = "vector"; std::unique_ptr hnsw_index; - const std::random_device::result_type seed = 14863; // fixed seed for reproducibility HnswIndexTest() { metadata.vector_type = redis::VectorType::FLOAT64; @@ -74,7 +73,7 @@ struct HnswIndexTest : TestBase { metadata.m = 3; metadata.distance_metric = redis::DistanceMetric::L2; auto search_key = redis::SearchKey(ns, idx_name, key); - hnsw_index = std::make_unique(search_key, &metadata, storage_.get(), seed); + hnsw_index = std::make_unique(search_key, &metadata, storage_.get()); } void TearDown() override { hnsw_index.reset(); } From 03c941f442ba1666a1c1e7238a71a89c28df6efa Mon Sep 17 00:00:00 2001 From: advisedy Date: Sat, 25 Jul 2026 17:32:33 +0800 Subject: [PATCH 5/5] fix(search): mark RandomizeLayer and InsertVectorEntry as const --- src/search/hnsw_indexer.cc | 4 ++-- src/search/hnsw_indexer.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/search/hnsw_indexer.cc b/src/search/hnsw_indexer.cc index 07fb41695dc..7fbc193e706 100644 --- a/src/search/hnsw_indexer.cc +++ b/src/search/hnsw_indexer.cc @@ -179,7 +179,7 @@ HnswIndex::HnswIndex(const SearchKey& search_key, HnswVectorFieldMetadata* vecto storage(storage), m_level_normalization_factor(1.0 / std::log(metadata->m)) {} -uint16_t HnswIndex::RandomizeLayer() { +uint16_t HnswIndex::RandomizeLayer() const { std::uniform_real_distribution level_dist(0.0, 1.0); double r = level_dist(generator); double log_val = -std::log(r); @@ -517,7 +517,7 @@ Status HnswIndex::InsertVectorEntryInternal(engine::Context& ctx, std::string_vi } Status HnswIndex::InsertVectorEntry(engine::Context& ctx, std::string_view key, const kqir::NumericArray& vector, - ObserverOrUniquePtr& batch) { + ObserverOrUniquePtr& batch) const { auto target_level = RandomizeLayer(); return InsertVectorEntryInternal(ctx, key, vector, batch, target_level); } diff --git a/src/search/hnsw_indexer.h b/src/search/hnsw_indexer.h index 53d5c6248a1..461fd8eb515 100644 --- a/src/search/hnsw_indexer.h +++ b/src/search/hnsw_indexer.h @@ -99,7 +99,7 @@ struct HnswIndex { const std::vector& node_key, uint16_t level, const SearchKey& search_key, const HnswVectorFieldMetadata* metadata); - uint16_t RandomizeLayer(); + uint16_t RandomizeLayer() const; StatusOr DefaultEntryPoint(engine::Context& ctx, uint16_t level) const; Status AddEdge(const NodeKey& node_key1, const NodeKey& node_key2, uint16_t layer, ObserverOrUniquePtr& batch) const; @@ -117,7 +117,7 @@ struct HnswIndex { Status InsertVectorEntryInternal(engine::Context& ctx, std::string_view key, const kqir::NumericArray& vector, ObserverOrUniquePtr& batch, uint16_t layer) const; Status InsertVectorEntry(engine::Context& ctx, std::string_view key, const kqir::NumericArray& vector, - ObserverOrUniquePtr& batch); + ObserverOrUniquePtr& batch) const; Status DeleteVectorEntry(engine::Context& ctx, std::string_view key, ObserverOrUniquePtr& batch) const; StatusOr> KnnSearch(engine::Context& ctx, const kqir::NumericArray& query_vector,