HNSW: narrow critical section during add to avoid lock contention (#4915)

Summary:
Pull Request resolved: https://github.com/facebookresearch/faiss/pull/4915

The `#pragma omp critical` block in `add_with_locks` was unconditionally entered on every insertion just to read `entry_point`. This serializes all threads at the start of every add, creating a bottleneck under high thread counts.

Narrow the critical section so that `entry_point` is read locklessly, and the lock is only acquired in the rare case where `entry_point == -1` (i.e., the very first insertion). A double-check pattern ensures correctness.

Reviewed By: mnorris11

Differential Revision: D94711667

fbshipit-source-id: 26c554b03ee3e5c4177fc8be6df84609e84a8c20
This commit is contained in:
Tom Jackson
2026-03-16 09:24:32 -07:00
committed by meta-codesync[bot]
parent b1feeb79f8
commit 9962fbec6e
+8 -7
View File
@@ -539,16 +539,16 @@ void HNSW::add_with_locks(
std::vector<omp_lock_t>& locks,
VisitedTable& vt,
bool keep_max_size_level0) {
// greedy search on upper levels
storage_idx_t nearest;
storage_idx_t nearest = entry_point;
if (nearest == -1) { // avoid locking after the first point.
#pragma omp critical
{
nearest = entry_point;
if (nearest == -1) {
if (entry_point == -1) { // double-check under lock.
max_level = pt_level;
entry_point = pt_id;
// leave nearest = -1 to trigger early exit after critical block.
} else {
// else: Another thread set the entry point.
nearest = entry_point;
}
}
@@ -561,6 +561,7 @@ void HNSW::add_with_locks(
int level = max_level; // level at which we start adding neighbors
float d_nearest = ptdis(nearest);
// greedy search on upper levels
for (; level > pt_level; level--) {
greedy_update_nearest(*this, ptdis, level, nearest, d_nearest);
}