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
This commit is contained in:
Timothy Herchen
2026-05-04 08:30:09 +02:00
committed by Joost VandeVondele
parent 1a882efc7f
commit 04ce2c04d7
+71 -1
View File
@@ -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<int>::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<int>::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;