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 786852312837c8ae6f6f9a040f338b3f3e5184c8 Mon Sep 17 00:00:00 2001 From: chanztuying Date: Fri, 21 Aug 2026 18:28:47 +0000 Subject: [PATCH 2/2] MDEV-38721: make the MHNSW prefetch portable to MSVC --- include/my_cpu.h | 19 +++++++++++++++++++ sql/vector_mhnsw.cc | 8 ++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/include/my_cpu.h b/include/my_cpu.h index 028dc05bd084a..66205de99a408 100644 --- a/include/my_cpu.h +++ b/include/my_cpu.h @@ -110,6 +110,25 @@ static inline void MY_RELAX_CPU(void) } +/* + Hint the CPU to start loading a cache line for reading. + + Like YieldProcessor() above, PreFetchCacheLine() is architecture-independent: + winnt.h picks the instruction per target, unlike _mm_prefetch(). + clang-cl defines __clang__ but not __GNUC__, so it must take the first branch. +*/ +static inline void my_prefetch(const void *addr) +{ +#if defined(__GNUC__) || defined(__clang__) + __builtin_prefetch(addr, 0, 3); +#elif defined(_WIN32) + PreFetchCacheLine(PF_TEMPORAL_LEVEL_1, addr); +#else + (void) addr; +#endif +} + + #ifdef HAVE_PAUSE_INSTRUCTION # ifdef __cplusplus extern "C" { diff --git a/sql/vector_mhnsw.cc b/sql/vector_mhnsw.cc index 0891fb0a1dce6..d42461cae6620 100644 --- a/sql/vector_mhnsw.cc +++ b/sql/vector_mhnsw.cc @@ -22,6 +22,7 @@ #include "vector_mhnsw.h" #include #include +#include // my_prefetch() #include "bloom_filters.h" // distance can be a little bit < 0 because of fast math @@ -1347,7 +1348,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 +1355,11 @@ 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); + my_prefetch(link); + my_prefetch(reinterpret_cast(link) + + CPU_LEVEL1_DCACHE_LINESIZE); } } -#endif for (size_t i= 0; i < 8; i++) {