From 04ce2c04d7e6b50d41123f67f286f59d1766790e Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Mon, 4 May 2026 08:30:09 +0200 Subject: [PATCH] Faster movepick sorting on AVX512ICL The core idea here is to greatly speed up the backwards insertion loop of the insertion sort. We can store up to 16 sorted moves and their values/scores in vectors. We obtain all moves with a smaller score (i.e., sorted later than the move-to-insert) as a mask, giving us something like `1111111111000000`. If we subtract one this gives us `1111111110111111` which can be can be used as an expand mask, to insert the new move. Passed STC: https://tests.stockfishchess.org/tests/view/69e9c9190e9667dd5a765853 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 45024 W: 11867 L: 11550 D: 21607 Ptnml(0-2): 101, 4938, 12142, 5205, 126 Local test: ``` Result of 50 runs ================== base (...kfish.master) = 2026071 +/- 5124 test (./stockfish ) = 2083770 +/- 5964 diff = +57699 +/- 8535 speedup = +0.0285 P(speedup > 0) = 1.0000 ``` closes https://github.com/official-stockfish/Stockfish/pull/6768 No functional change --- src/movepick.cpp | 72 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 0c31761cb..e499b745a 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -56,12 +56,82 @@ enum Stages { QCAPTURE }; +#ifdef USE_AVX512ICL +// Load the Move, and the ExtMove value, into all lanes of 512-bit registers +static void splat_extmove(const ExtMove& m, __m512i& move, __m512i& value) { + move = _mm512_set1_epi32(m.raw()); + value = _mm512_set1_epi32(m.value); +} + +// Sorts up to 16 moves. +struct MoveSorter { + static constexpr int MAX_ELEMENTS = 16; + __m512i sortedValues, sortedMoves; + + explicit MoveSorter(const ExtMove& first) { + splat_extmove(first, sortedMoves, sortedValues); + + // Set the uninitialized move values to INT_MIN, so that they sort less than any other move + sortedValues = _mm512_mask_set1_epi32(sortedValues, ~1, std::numeric_limits::min()); + } + + void insert(const ExtMove& m) { + __m512i move, value; + splat_extmove(m, move, value); + + // Mask of all elements except the insertion point + assert(m.value != std::numeric_limits::min()); + const uint16_t expand = _kadd_mask16(_mm512_cmplt_epi32_mask(sortedValues, value), -1); + + sortedValues = _mm512_mask_expand_epi32(value, expand, sortedValues); + sortedMoves = _mm512_mask_expand_epi32(move, expand, sortedMoves); + } + + void write_sorted(ExtMove* moves, std::ptrdiff_t count) const { + static_assert(sizeof(ExtMove) == 8); + assert(count <= MAX_ELEMENTS); + + // Because values and moves are stored separately, we need to reassemble the ExtMoves + auto write = [&](int offset, const __m512i indices) { + const __m512i extMoves = _mm512_permutex2var_epi32(sortedMoves, indices, sortedValues); + const std::ptrdiff_t storeCount = count - offset; + + if (storeCount > 0) + _mm512_mask_storeu_epi64(moves + offset, (1 << storeCount) - 1, extMoves); + }; + + write(0, _mm512_setr_epi32(0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23)); + write(8, _mm512_setr_epi32(8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13, 29, 14, 30, 15, 31)); + } +}; +#endif // Sort moves in descending order up to and including a given limit. // The order of moves smaller than the limit is left unspecified. void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) { + ExtMove *sortedEnd = begin, *p = begin + 1; - for (ExtMove *sortedEnd = begin, *p = begin + 1; p < end; ++p) +#ifdef USE_AVX512ICL + if (begin == end) + return; + + MoveSorter sorter(*begin); + for (; p < end; ++p) + { + if (p->value >= limit) + { + if (sortedEnd - begin + 1 >= MoveSorter::MAX_ELEMENTS) // sorter full + break; + + sorter.insert(*p); + *p = *++sortedEnd; + } + } + sorter.write_sorted(begin, sortedEnd - begin + 1); + // Use scalar implementation for any remaining elements +#endif + + for (; p < end; ++p) if (p->value >= limit) { ExtMove tmp = *p, *q;