Speedup movegen with VBMI2

Passed STC
LLR: 2.96 (-2.94,2.94) <0.00,2.00>
Total: 166720 W: 43191 L: 42701 D: 80828
Ptnml(0-2): 348, 18567, 45069, 18999, 377
https://tests.stockfishchess.org/tests/view/686ae98dfe0f2fe354c0c867

Refactor movegen to emit to a vector with 16-bit elements, which enables a
speedup with AVX512-VBMI2 when writing moves to the move list.

Very crude timing measurements of perft via timing ./stockfish "go perft 7"
demonstrates approximately 17% perft speedup:

Summary
  ./Stockfish-dev/src/stockfish 'go perft 7' ran
    1.17 ± 0.04 times faster than ./Stockfish-base/src/stockfish 'go perft 7'

Estimated overall nps increase of 0.4% via speedtest: 33605229 -> 33749825
(many thanks JonathanHallstrom).

The corresponding arch is avx512icl as it is a good baseline for consumer
avx-512 feature set support; Intel Ice Lake was the first consumer AVX-512 CPU
and it is a decent subset of what AMD Zen 4 supports.

closes https://github.com/official-stockfish/Stockfish/pull/6153

No functional change
This commit is contained in:
87
2025-07-24 10:08:35 +02:00
committed by Joost VandeVondele
parent c13f883dc4
commit 8c2d21f91a
8 changed files with 156 additions and 60 deletions
+1
View File
@@ -10,6 +10,7 @@ Motohiro Isozaki (yaneurao)
Hisayori Noda (nodchip)
# All other authors of Stockfish code (in alphabetical order)
87flowers
Aditya (absimaldata)
Adrian Petrescu (apetresc)
Ahmed Kerimov (wcdbmv)
+3 -1
View File
@@ -39,7 +39,9 @@ set_arch_loongarch64() {
# Set the file CPU x86_64 architecture
set_arch_x86_64() {
if check_flags 'avx512vnni' 'avx512dq' 'avx512f' 'avx512bw' 'avx512vl'; then
if check_flags 'avx512f' 'avx512cd' 'avx512vl' 'avx512dq' 'avx512bw' 'avx512ifma' 'avx512vbmi' 'avx512vbmi2' 'avx512vpopcntdq' 'avx512bitalg' 'avx512vnni' 'vpclmulqdq' 'gfni' 'vaes'; then
true_arch='x86-64-avx512icl'
elif check_flags 'avx512vnni' 'avx512dq' 'avx512f' 'avx512bw' 'avx512vl'; then
true_arch='x86-64-vnni256'
elif check_flags 'avx512f' 'avx512bw'; then
true_arch='x86-64-avx512'
+28 -3
View File
@@ -99,6 +99,7 @@ VPATH = syzygy:nnue:nnue/features
# avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512
# vnni256 = yes/no --- -mavx256vnni --- Use Intel Vector Neural Network Instructions 512 with 256bit operands
# vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512
# avx512icl = yes/no --- ... multiple ... --- Use All AVX-512 features available on both Intel Ice Lake and AMD Zen 4
# altivec = yes/no --- -maltivec --- Use PowerPC Altivec SIMD extension
# vsx = yes/no --- -mvsx --- Use POWER VSX SIMD extension
# neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture
@@ -125,10 +126,10 @@ ifeq ($(ARCH), native)
endif
# explicitly check for the list of supported architectures (as listed with make help),
# the user can override with `make ARCH=x86-32-vnni256 SUPPORTED_ARCH=true`
# the user can override with `make ARCH=x86-64-avx512icl SUPPORTED_ARCH=true`
ifeq ($(ARCH), $(filter $(ARCH), \
x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-avxvnni x86-64-bmi2 \
x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \
x86-64-avx512icl x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-avxvnni \
x86-64-bmi2 x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \
x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-64-altivec ppc-64-vsx ppc-32 e2k \
armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64 \
loongarch64 loongarch64-lsx loongarch64-lasx))
@@ -154,6 +155,7 @@ avxvnni = no
avx512 = no
vnni256 = no
vnni512 = no
avx512icl = no
altivec = no
vsx = no
neon = no
@@ -290,6 +292,19 @@ ifeq ($(findstring -vnni512,$(ARCH)),-vnni512)
vnni512 = yes
endif
ifeq ($(findstring -avx512icl,$(ARCH)),-avx512icl)
popcnt = yes
sse = yes
sse2 = yes
ssse3 = yes
sse41 = yes
avx2 = yes
pext = yes
avx512 = yes
vnni512 = yes
avx512icl = yes
endif
ifeq ($(sse),yes)
prefetch = yes
endif
@@ -719,6 +734,13 @@ ifeq ($(vnni512),yes)
endif
endif
ifeq ($(avx512icl),yes)
CXXFLAGS += -DUSE_AVX512 -DUSE_VNNI -DUSE_AVX512ICL
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -mavx512f -mavx512cd -mavx512vl -mavx512dq -mavx512bw -mavx512ifma -mavx512vbmi -mavx512vbmi2 -mavx512vpopcntdq -mavx512bitalg -mavx512vnni -mvpclmulqdq -mgfni -mvaes
endif
endif
ifeq ($(sse41),yes)
CXXFLAGS += -DUSE_SSE41
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
@@ -877,6 +899,7 @@ help:
echo "Supported archs:" && \
echo "" && \
echo "native > select the best architecture for the host processor (default)" && \
echo "x86-64-avx512icl > x86 64-bit with minimum avx512 support of Intel Ice Lane or AMD Zen 4" && \
echo "x86-64-vnni512 > x86 64-bit with vnni 512bit support" && \
echo "x86-64-vnni256 > x86 64-bit with vnni 512bit support, limit operands to 256bit wide" && \
echo "x86-64-avx512 > x86 64-bit with avx512 support" && \
@@ -1025,6 +1048,7 @@ config-sanity: net
echo "avx512: '$(avx512)'" && \
echo "vnni256: '$(vnni256)'" && \
echo "vnni512: '$(vnni512)'" && \
echo "avx512icl: '$(avx512icl)'" && \
echo "altivec: '$(altivec)'" && \
echo "vsx: '$(vsx)'" && \
echo "neon: '$(neon)'" && \
@@ -1061,6 +1085,7 @@ config-sanity: net
(test "$(avx512)" = "yes" || test "$(avx512)" = "no") && \
(test "$(vnni256)" = "yes" || test "$(vnni256)" = "no") && \
(test "$(vnni512)" = "yes" || test "$(vnni512)" = "no") && \
(test "$(avx512icl)" = "yes" || test "$(avx512icl)" = "no") && \
(test "$(altivec)" = "yes" || test "$(altivec)" = "no") && \
(test "$(vsx)" = "yes" || test "$(vsx)" = "no") && \
(test "$(neon)" = "yes" || test "$(neon)" = "no") && \
+3
View File
@@ -237,6 +237,9 @@ std::string compiler_info() {
compiler += "\nCompilation settings : ";
compiler += (Is64Bit ? "64bit" : "32bit");
#if defined(USE_AVX512ICL)
compiler += " AVX512ICL";
#endif
#if defined(USE_VNNI)
compiler += " VNNI";
#endif
+93 -38
View File
@@ -24,12 +24,88 @@
#include "bitboard.h"
#include "position.h"
#if defined(USE_AVX512ICL)
#include <array>
#include <algorithm>
#include <immintrin.h>
#endif
namespace Stockfish {
namespace {
#if defined(USE_AVX512ICL)
inline Move* write_moves(Move* moveList, uint32_t mask, __m512i vector) {
_mm512_storeu_si512(reinterpret_cast<__m512i*>(moveList),
_mm512_maskz_compress_epi16(mask, vector));
return moveList + popcount(mask);
}
template<Direction offset>
inline Move* splat_pawn_moves(Move* moveList, Bitboard to_bb) {
alignas(64) static constexpr auto SPLAT_TABLE = [] {
std::array<Move, 64> table{};
for (int8_t i = 0; i < 64; i++)
{
Square from{std::clamp<int8_t>(i - offset, 0, 63)};
table[i] = {Move(from, Square{i})};
}
return table;
}();
auto table = reinterpret_cast<const __m512i*>(SPLAT_TABLE.data());
moveList =
write_moves(moveList, static_cast<uint32_t>(to_bb >> 0), _mm512_load_si512(table + 0));
moveList =
write_moves(moveList, static_cast<uint32_t>(to_bb >> 32), _mm512_load_si512(table + 1));
return moveList;
}
inline Move* splat_moves(Move* moveList, Square from, Bitboard to_bb) {
alignas(64) static constexpr auto SPLAT_TABLE = [] {
std::array<Move, 64> table{};
for (int8_t i = 0; i < 64; i++)
table[i] = {Move(SQUARE_ZERO, Square{i})};
return table;
}();
__m512i fromVec = _mm512_set1_epi16(Move(from, SQUARE_ZERO).raw());
auto table = reinterpret_cast<const __m512i*>(SPLAT_TABLE.data());
moveList = write_moves(moveList, static_cast<uint32_t>(to_bb >> 0),
_mm512_or_si512(_mm512_load_si512(table + 0), fromVec));
moveList = write_moves(moveList, static_cast<uint32_t>(to_bb >> 32),
_mm512_or_si512(_mm512_load_si512(table + 1), fromVec));
return moveList;
}
#else
template<Direction offset>
inline Move* splat_pawn_moves(Move* moveList, Bitboard to_bb) {
while (to_bb)
{
Square to = pop_lsb(to_bb);
*moveList++ = Move(to - offset, to);
}
return moveList;
}
inline Move* splat_moves(Move* moveList, Square from, Bitboard to_bb) {
while (to_bb)
*moveList++ = Move(from, pop_lsb(to_bb));
return moveList;
}
#endif
template<GenType Type, Direction D, bool Enemy>
ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) {
Move* make_promotions(Move* moveList, [[maybe_unused]] Square to) {
constexpr bool all = Type == EVASIONS || Type == NON_EVASIONS;
@@ -48,7 +124,7 @@ ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) {
template<Color Us, GenType Type>
ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
Move* generate_pawn_moves(const Position& pos, Move* moveList, Bitboard target) {
constexpr Color Them = ~Us;
constexpr Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB);
@@ -75,17 +151,8 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta
b2 &= target;
}
while (b1)
{
Square to = pop_lsb(b1);
*moveList++ = Move(to - Up, to);
}
while (b2)
{
Square to = pop_lsb(b2);
*moveList++ = Move(to - Up - Up, to);
}
moveList = splat_pawn_moves<Up>(moveList, b1);
moveList = splat_pawn_moves<Up + Up>(moveList, b2);
}
// Promotions and underpromotions
@@ -114,17 +181,8 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta
Bitboard b1 = shift<UpRight>(pawnsNotOn7) & enemies;
Bitboard b2 = shift<UpLeft>(pawnsNotOn7) & enemies;
while (b1)
{
Square to = pop_lsb(b1);
*moveList++ = Move(to - UpRight, to);
}
while (b2)
{
Square to = pop_lsb(b2);
*moveList++ = Move(to - UpLeft, to);
}
moveList = splat_pawn_moves<UpRight>(moveList, b1);
moveList = splat_pawn_moves<UpLeft>(moveList, b2);
if (pos.ep_square() != SQ_NONE)
{
@@ -148,7 +206,7 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta
template<Color Us, PieceType Pt>
ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
Move* generate_moves(const Position& pos, Move* moveList, Bitboard target) {
static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()");
@@ -159,8 +217,7 @@ ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target)
Square from = pop_lsb(bb);
Bitboard b = attacks_bb<Pt>(from, pos.pieces()) & target;
while (b)
*moveList++ = Move(from, pop_lsb(b));
moveList = splat_moves(moveList, from, b);
}
return moveList;
@@ -168,7 +225,7 @@ ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target)
template<Color Us, GenType Type>
ExtMove* generate_all(const Position& pos, ExtMove* moveList) {
Move* generate_all(const Position& pos, Move* moveList) {
static_assert(Type != LEGAL, "Unsupported type in generate_all()");
@@ -192,8 +249,7 @@ ExtMove* generate_all(const Position& pos, ExtMove* moveList) {
Bitboard b = attacks_bb<KING>(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target);
while (b)
*moveList++ = Move(ksq, pop_lsb(b));
moveList = splat_moves(moveList, ksq, b);
if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING))
for (CastlingRights cr : {Us & KING_SIDE, Us & QUEEN_SIDE})
@@ -213,7 +269,7 @@ ExtMove* generate_all(const Position& pos, ExtMove* moveList) {
//
// Returns a pointer to the end of the move list.
template<GenType Type>
ExtMove* generate(const Position& pos, ExtMove* moveList) {
Move* generate(const Position& pos, Move* moveList) {
static_assert(Type != LEGAL, "Unsupported type in generate()");
assert((Type == EVASIONS) == bool(pos.checkers()));
@@ -225,21 +281,20 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) {
}
// Explicit template instantiations
template ExtMove* generate<CAPTURES>(const Position&, ExtMove*);
template ExtMove* generate<QUIETS>(const Position&, ExtMove*);
template ExtMove* generate<EVASIONS>(const Position&, ExtMove*);
template ExtMove* generate<NON_EVASIONS>(const Position&, ExtMove*);
template Move* generate<CAPTURES>(const Position&, Move*);
template Move* generate<QUIETS>(const Position&, Move*);
template Move* generate<EVASIONS>(const Position&, Move*);
template Move* generate<NON_EVASIONS>(const Position&, Move*);
// generate<LEGAL> generates all the legal moves in the given position
template<>
ExtMove* generate<LEGAL>(const Position& pos, ExtMove* moveList) {
Move* generate<LEGAL>(const Position& pos, Move* moveList) {
Color us = pos.side_to_move();
Bitboard pinned = pos.blockers_for_king(us) & pos.pieces(us);
Square ksq = pos.square<KING>(us);
ExtMove* cur = moveList;
Move* cur = moveList;
moveList =
pos.checkers() ? generate<EVASIONS>(pos, moveList) : generate<NON_EVASIONS>(pos, moveList);
+6 -6
View File
@@ -49,7 +49,7 @@ struct ExtMove: public Move {
inline bool operator<(const ExtMove& f, const ExtMove& s) { return f.value < s.value; }
template<GenType>
ExtMove* generate(const Position& pos, ExtMove* moveList);
Move* generate(const Position& pos, Move* moveList);
// The MoveList struct wraps the generate() function and returns a convenient
// list of moves. Using MoveList is sometimes preferable to directly calling
@@ -59,13 +59,13 @@ struct MoveList {
explicit MoveList(const Position& pos) :
last(generate<T>(pos, moveList)) {}
const ExtMove* begin() const { return moveList; }
const ExtMove* end() const { return last; }
size_t size() const { return last - moveList; }
bool contains(Move move) const { return std::find(begin(), end(), move) != end(); }
const Move* begin() const { return moveList; }
const Move* end() const { return last; }
size_t size() const { return last - moveList; }
bool contains(Move move) const { return std::find(begin(), end(), move) != end(); }
private:
ExtMove moveList[MAX_MOVES], *last;
Move moveList[MAX_MOVES], *last;
};
} // namespace Stockfish
+20 -10
View File
@@ -123,7 +123,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, int th, const CapturePieceTo
// Captures are ordered by Most Valuable Victim (MVV), preferring captures
// with a good history. Quiets moves are ordered using the history tables.
template<GenType Type>
void MovePicker::score() {
ExtMove* MovePicker::score(MoveList<Type>& ml) {
static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type");
@@ -138,8 +138,12 @@ void MovePicker::score() {
threatByLesser[QUEEN] = pos.attacks_by<ROOK>(~us) | threatByLesser[ROOK];
}
for (auto& m : *this)
ExtMove* it = cur;
for (auto move : ml)
{
ExtMove& m = *it++;
m = move;
const Square from = m.from_sq();
const Square to = m.to_sq();
const Piece pc = pos.moved_piece(m);
@@ -189,6 +193,7 @@ void MovePicker::score() {
}
}
}
return it;
}
// Returns the next move satisfying a predicate function.
@@ -222,14 +227,16 @@ top:
case CAPTURE_INIT :
case PROBCUT_INIT :
case QCAPTURE_INIT :
case QCAPTURE_INIT : {
MoveList<CAPTURES> ml(pos);
cur = endBadCaptures = moves;
endCur = endCaptures = generate<CAPTURES>(pos, cur);
endCur = endCaptures = score<CAPTURES>(ml);
score<CAPTURES>();
partial_insertion_sort(cur, endCur, std::numeric_limits<int>::min());
++stage;
goto top;
}
case GOOD_CAPTURE :
if (select([&]() {
@@ -246,9 +253,10 @@ top:
case QUIET_INIT :
if (!skipQuiets)
{
endCur = endGenerated = generate<QUIETS>(pos, cur);
MoveList<QUIETS> ml(pos);
endCur = endGenerated = score<QUIETS>(ml);
score<QUIETS>();
partial_insertion_sort(cur, endCur, -3560 * depth);
}
@@ -283,14 +291,16 @@ top:
return Move::none();
case EVASION_INIT :
case EVASION_INIT : {
MoveList<EVASIONS> ml(pos);
cur = moves;
endCur = endGenerated = generate<EVASIONS>(pos, cur);
endCur = endGenerated = score<EVASIONS>(ml);
score<EVASIONS>();
partial_insertion_sort(cur, endCur, std::numeric_limits<int>::min());
++stage;
[[fallthrough]];
}
case EVASION :
case QCAPTURE :
+2 -2
View File
@@ -55,8 +55,8 @@ class MovePicker {
private:
template<typename Pred>
Move select(Pred);
template<GenType>
void score();
template<GenType T>
ExtMove* score(MoveList<T>&);
ExtMove* begin() { return cur; }
ExtMove* end() { return endCur; }