From 8c0ba5264fdd78d940af4adb3df8d8a17e89339e Mon Sep 17 00:00:00 2001 From: chanztuying Date: Tue, 18 Aug 2026 17:12:20 +0000 Subject: [PATCH 1/2] MDEV-38721: prefetch MHNSW neighbours during search Issue prefetches for unseen neighbour nodes before evaluating their distances. This overlaps later memory loads with distance calculations for earlier lanes without changing the search result. On a fixed 200k by 1024-dimensional cosine graph with M=6, a crossed AB/BA warm-cache run (30 paired observations) improved paired median QPS by 10.1% at ef_search=40 (95% CI 8.0%-14.4%) and 13.8% at ef_search=160 (95% CI 11.8%-20.0%), with identical exact-recall means. --- sql/vector_mhnsw.cc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sql/vector_mhnsw.cc b/sql/vector_mhnsw.cc index c480c36c7e7ad..0891fb0a1dce6 100644 --- a/sql/vector_mhnsw.cc +++ b/sql/vector_mhnsw.cc @@ -1347,6 +1347,20 @@ static int search_layer(MHNSW_param *p, const FVector *target, float threshold, if (res == 0xff) continue; +#if defined(__GNUC__) + // A node and its vector share one allocation. Prefetch unseen nodes before + // computing distances so later loads can overlap with work on earlier ones. + for (size_t i= 0; i < 8; i++) + { + FVectorNode *link= links[i]; + if (!(res & (1 << i)) && link) + { + __builtin_prefetch(link, 0, 3); + __builtin_prefetch(reinterpret_cast(link) + 64, 0, 3); + } + } +#endif + for (size_t i= 0; i < 8; i++) { if (res & (1 << i)) From edf9a46fac1145e5c6ef95371ff962a93473a80a Mon Sep 17 00:00:00 2001 From: chanztuying Date: Wed, 19 Aug 2026 10:31:20 +0000 Subject: [PATCH 2/2] Wrap prefetch in MHNSW_PREFETCH_READ() and implements MSVC prefetching --- sql/vector_mhnsw.cc | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/sql/vector_mhnsw.cc b/sql/vector_mhnsw.cc index 0891fb0a1dce6..475bdf007ec9b 100644 --- a/sql/vector_mhnsw.cc +++ b/sql/vector_mhnsw.cc @@ -36,6 +36,22 @@ static constexpr float subdist_margin= 1.05f; static constexpr double subdist_stddev_threshold= 0.05; // 3σ, p>99.9% static constexpr ulonglong subdist_stddev_valid= 10000; // sufficient +/* Portable read prefetch, modeled after UNIV_PREFETCH_R. */ +#if defined(__GNUC__) || defined(__clang__) +# define MHNSW_PREFETCH_READ(addr) __builtin_prefetch((addr), 0, 3) +#elif defined(_MSC_VER) +# include +# if defined(_M_IX86) || defined(_M_X64) +# define MHNSW_PREFETCH_READ(addr) \ + _mm_prefetch(reinterpret_cast(addr), _MM_HINT_T0) +# elif defined(_M_ARM64) +# define MHNSW_PREFETCH_READ(addr) __prefetch(addr) +# endif +#endif +#ifndef MHNSW_PREFETCH_READ +# define MHNSW_PREFETCH_READ(addr) ((void) 0) +#endif + /* The class below can assume normal distribution and only collect M1 and M2, or go beyond that and collect M3 and M4 to account @@ -1347,7 +1363,6 @@ static int search_layer(MHNSW_param *p, const FVector *target, float threshold, if (res == 0xff) continue; -#if defined(__GNUC__) // A node and its vector share one allocation. Prefetch unseen nodes before // computing distances so later loads can overlap with work on earlier ones. for (size_t i= 0; i < 8; i++) @@ -1355,11 +1370,10 @@ static int search_layer(MHNSW_param *p, const FVector *target, float threshold, FVectorNode *link= links[i]; if (!(res & (1 << i)) && link) { - __builtin_prefetch(link, 0, 3); - __builtin_prefetch(reinterpret_cast(link) + 64, 0, 3); + MHNSW_PREFETCH_READ(link); + MHNSW_PREFETCH_READ(reinterpret_cast(link) + 64); } } -#endif for (size_t i= 0; i < 8; i++) {