Skip to content

[8.2] MOD-14916 Devirtualize HNSW / brute-force search hot path#940

Merged
ofiryanai merged 1 commit into8.2from
backport-937-to-8.2
Apr 21, 2026
Merged

[8.2] MOD-14916 Devirtualize HNSW / brute-force search hot path#940
ofiryanai merged 1 commit into8.2from
backport-937-to-8.2

Conversation

@ofiryanai
Copy link
Copy Markdown
Collaborator

@ofiryanai ofiryanai commented Apr 20, 2026

Description

Backport of #937 to 8.2.


Pull Request opened by Augment Code with guidance from the PR author


Note

Medium Risk
Touches core HNSW/brute-force search hot paths by bypassing virtual dispatch and caching function pointers; incorrect assumptions (e.g., vectors type or null cachedDistFunc) could lead to crashes or subtle runtime issues.

Overview
Devirtualizes VecSim search hot paths by bypassing RawDataContainer/calculator vtables.

HNSW and brute-force now fetch vector data via an explicit cast to DataBlocksContainer in getDataByInternalId, and DataBlocksContainer::getElement is inlined (moved from .cpp to header) to help the compiler fold indexing arithmetic.

VecSimIndexAbstract now caches the raw distance function pointer (cachedDistFunc) from IndexCalculatorInterface::getDistFunc() and uses it in calcDistance instead of virtual indexCalculator->calcDistance; unit tests update the dummy calculator to return nullptr for this new API when a nonstandard signature is used.

Reviewed by Cursor Bugbot for commit c02982a. Bugbot is set up for automated code reviews on this repo. Configure here.

* MOD-14916 Devirtualize distance + getElement on HNSW search hot path

MOD-14916 / LTK perf investigation.

Two virtual dispatches per HNSW candidate added between v2.10.21 and
v8.2.6 account for a measurable share of the KNN regression observed
in the LTK benchmarks (-38% throughput on Intel). Both are removed
here with the minimum possible change.

V1 - distance computation:
    Every calcDistance() call goes through IndexCalculatorInterface's
    vtable to reach DistanceCalculatorCommon, which then calls the
    underlying SIMD function pointer. The intermediate vtable hop is
    pure indirection; the concrete calculator class is fixed for the
    life of an index.

    Expose the underlying dist_func via a new pure-virtual
    getDistFunc() on IndexCalculatorInterface, implemented by
    DistanceCalculatorCommon. Cache the returned function pointer in
    VecSimIndexAbstract at construction time and call it directly in
    calcDistance(), bypassing the virtual dispatch.

V2 - vector fetch:
    HNSWIndex::getDataByInternalId and BruteForceIndex::getDataByInternalId
    call this->vectors->getElement(id), which is virtual through the
    RawDataContainer base. DataBlocksContainer is the only concrete
    implementation, and this->vectors is always a DataBlocksContainer
    (created and owned by VecSimIndexAbstract's constructor).

    Use a static_cast to DataBlocksContainer* plus a qualified call to
    DataBlocksContainer::getElement to skip the vtable lookup.

No behavior change; per-candidate distance and neighbor-fetch calls
on HNSW / brute force search paths become direct function-pointer /
direct-member calls. Headers in index_factories, hnsw_serializer,
and brute_force_factory compile cleanly.

* MOD-14916 Inline DataBlocksContainer::getElement on HNSW search hot path

Follow-up to the previous V1/V2 devirt commit. The static_cast+qualified
call in getDataByInternalId removed the vtable lookup but left the larger
cost on the table: DataBlocksContainer::getElement was still defined in
data_blocks_container.cpp, so every per-candidate neighbor fetch still
paid a real out-of-line function call and a bounds-checked blocks.at()
lookup. Without LTO the compiler could neither inline the body nor hoist
the div/mod in the HNSW hot loop.

Move the definition into the header as inline and drop the .at() bounds
check to match the v2.10.21 baseline, which used unchecked operator[] and
was fully inlined into processCandidate.

Also add a getDistFunc() override to DistanceCalculatorDummy in
test_components.cpp so BUILD_TESTS still compiles after the pure virtual
added in the previous commit.

(cherry picked from commit 4ca500a)
@jit-ci
Copy link
Copy Markdown

jit-ci Bot commented Apr 20, 2026

🛡️ Jit Security Scan Results

CRITICAL HIGH MEDIUM

✅ No security findings were detected in this PR


Security scan by Jit

@ofiryanai ofiryanai marked this pull request as ready for review April 20, 2026 12:55
@ofiryanai ofiryanai requested a review from GuyAv46 April 20, 2026 12:55
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.96%. Comparing base (ead7fee) to head (c02982a).
⚠️ Report is 1 commits behind head on 8.2.

Additional details and impacted files
@@            Coverage Diff             @@
##              8.2     #940      +/-   ##
==========================================
- Coverage   96.98%   96.96%   -0.03%     
==========================================
  Files         126      126              
  Lines        7366     7371       +5     
==========================================
+ Hits         7144     7147       +3     
- Misses        222      224       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ofiryanai ofiryanai enabled auto-merge April 20, 2026 13:36
@ofiryanai ofiryanai added this pull request to the merge queue Apr 20, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to no response for status checks Apr 20, 2026
@ofiryanai ofiryanai added this pull request to the merge queue Apr 21, 2026
Merged via the queue into 8.2 with commit 6f4f966 Apr 21, 2026
21 of 23 checks passed
@ofiryanai ofiryanai deleted the backport-937-to-8.2 branch April 21, 2026 06:31
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.

2 participants