Skip to content

perf(search): share a thread-local RNG for HNSW and drop seed (#2398)#3562

Open
advisedy wants to merge 7 commits into
apache:unstablefrom
advisedy:#2398
Open

perf(search): share a thread-local RNG for HNSW and drop seed (#2398)#3562
advisedy wants to merge 7 commits into
apache:unstablefrom
advisedy:#2398

Conversation

@advisedy

Copy link
Copy Markdown
Contributor

Fixes #2398.

Problem

HnswIndex currently owns a std::mt19937 generator directly, so every
HnswIndex construction also constructs the random generator.

However, the generator is only needed by RandomizeLayer() when inserting
vector entries. HnswIndex can also be constructed on query paths such as
KNN/RANGE search, where random layer generation is not used.

Fix

  • Replace the eager std::mt19937 member with a lazily initialized
    std::unique_ptr<std::mt19937>.
  • Initialize the generator only on the first RandomizeLayer() call.

Tests

new C++ gtests added (hnsw_index_test.cc):

  • RandomGeneratorIsInitializedLazily

AI-assisted contribution

I used an LLM to help draft the new GTest case for verifying lazy generator
initialization.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes redis::HnswIndex construction cost on query paths by avoiding eager construction of a large std::mt19937 member, and instead initializing the RNG only when layer randomization is actually needed during inserts.

Changes:

  • Replace the eager std::mt19937 member with a lazily initialized std::unique_ptr<std::mt19937> plus an optional stored seed.
  • Initialize the RNG on first RandomizeLayer() call rather than in the constructor.
  • Add a new C++ gtest that asserts the RNG is not initialized until RandomizeLayer() is invoked.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/search/hnsw_indexer.h Changes HnswIndex RNG-related members/signature to support lazy initialization.
src/search/hnsw_indexer.cc Implements lazy RNG allocation/initialization inside RandomizeLayer().
tests/cppunit/hnsw_index_test.cc Adds a regression test to verify RNG is initialized lazily.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/search/hnsw_indexer.cc Outdated
advisedy and others added 2 commits July 20, 2026 17:22
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
38.3% Coverage on New Code (required ≥ 50%)
D Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

💡 Need a hand with PR review? Try Gitar by Sonar!

jihuayu
jihuayu previously approved these changes Jul 21, 2026

@jihuayu jihuayu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! LGTM

@jihuayu
jihuayu requested review from PragmaTwice and git-hulk July 21, 2026 01:09
@advisedy

Copy link
Copy Markdown
Contributor Author

Hi, @git-hulk , @PragmaTwice ,just friendly ping on this one too, whenever you have some time. Thanks!

Comment thread src/search/hnsw_indexer.cc Outdated
uint16_t HnswIndex::RandomizeLayer() {
if (!generator) {
const auto actual_seed = seed ? *seed : std::random_device()();
generator = std::make_unique<std::mt19937>(actual_seed);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the original issue, it should expect to keep only one random generator for all HNSW indexes? @PragmaTwice

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. maybe thread local to avoid race.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

switched to a single static thread_local generator as you suggested. please take a look.

@PragmaTwice PragmaTwice left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refer to the issue, I think it's better to make the RNG static.

@advisedy

Copy link
Copy Markdown
Contributor Author

I was being silly and misunderstood,Now fixed: switched to a single static thread_local generator as you suggested. Updated in the latest push, please take a look @PragmaTwice .

@advisedy
advisedy requested a review from PragmaTwice July 23, 2026 13:17
@git-hulk
git-hulk requested a review from jihuayu July 23, 2026 13:41
Comment thread src/search/hnsw_indexer.cc Outdated
Comment on lines +175 to +183
std::optional<std::random_device::result_type> 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() {
static thread_local std::mt19937 generator = [this] { return std::mt19937(seed.value_or(std::random_device()())); }();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Please don't make it a local static variable;
  2. The semantics of seed is very weird here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right — the semantics of seed is indeed weird here. Since the
generator is now shared, only the first instance's seed would take
effect, which is misleading.

I see two possible directions:

  1. Share the generator only among indexes with the same seed, and let
    indexes with different seeds keep their own. I haven't fully figured
    out the implementation yet, but maybe a static thread_local map
    could work? It seems like that might be doable.

  2. Drop seed entirely, seed the generator with std::random_device,
    and make it a class-level static thread_local std::mt19937 member
    (no longer a function-local static), so its initialization no longer
    depends on any specific instance.

I'd like to know which direction you think we should take. If you
prefer option 1, I'll think about how to implement it in detail. If
it's option 2, I'll go ahead and do it now.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you stop copy-pasting to me what your agent replies to you? You can do your own decision. I have enough token that I don't need an agent proxy.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, my english is not good, so I tell AI what I was thinking and let it translate. But The decision is mine. please.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is my origin words: 确实感觉seed有点奇怪在这里,但是generator得根据seed去生成出来,不如说这里seed,generator,和我们期望共享的逻辑有点混乱,我的理解有两种思路,第一种思路就是我们考虑对于seed一样的进行共享,不一样的就是各自持有,这种方法用static + thread_local + map 应该能实现出来? 但我感觉有点麻烦。第二种思路就是或许可以考虑直接把seed删掉,使用std::random_device,这样的话,static thread_local std::mt19937 generator; 放到类里面,不再是局部了,这样初始化也不会依赖某个实例。 想知道一下,你觉得我们应该怎么做会比较好呢?如果你想要第一种的思路,我再去思考具体该怎么实现会比较好。如果是第二种的话,我可以现在就去实现出来代码。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m very sorry that I relied too much on AI when replying, but my genuine intention was to contribute. I’m truly sorry.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I think we can just remove seed here.
  2. After the change, don't forget to rewrite the PR title and description to match the current implementation.

@advisedy advisedy changed the title perf(search): lazily initialize HNSW random generator (#2398) perf(search): share a thread-local RNG for HNSW and drop seed (#2398) Jul 23, 2026
@advisedy
advisedy requested a review from PragmaTwice July 23, 2026 16:57
@jihuayu

jihuayu commented Jul 24, 2026

Copy link
Copy Markdown
Member

Hi @advisedy.

There is nothing wrong with using AI to translate your writing into English, but do not let it turn a simple point into something unnecessarily long and bloated.

No one wants to feel as though they are talking to an AI agent. Copying and pasting an AI-generated response without editing it shows little respect for other people’s time. You need to think through what the AI gives you, incorporate your own understanding, and express it in your own words.

We are not short on tokens, but we do not need robots either. We are not looking for contributors who are only here to meet a KPI. We want contributors to grow with the community and remain part of it over the long term. That requires you to spend real time and effort thinking about and understanding the project, rather than relying entirely on AI to complete your tasks.

It is okay if you do not do everything well at the beginning, so there is no need to worry too much. I hope you can improve over time.

@PragmaTwice PragmaTwice left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code is good now. To get merged you need to check why the lint failed and fix it (just to add a const specifier).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Avoid HnswIndex heavy construction because of mt19937

5 participants