From 8c2d21f91a5840a67c36267e5043070ffad06860 Mon Sep 17 00:00:00 2001 From: 87 Date: Sun, 6 Jul 2025 18:18:35 +0100 Subject: [PATCH] Speedup movegen with VBMI2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- AUTHORS | 1 + scripts/get_native_properties.sh | 4 +- src/Makefile | 31 +++++++- src/misc.cpp | 3 + src/movegen.cpp | 131 ++++++++++++++++++++++--------- src/movegen.h | 12 +-- src/movepick.cpp | 30 ++++--- src/movepick.h | 4 +- 8 files changed, 156 insertions(+), 60 deletions(-) diff --git a/AUTHORS b/AUTHORS index be88a8e96..b43a9e5b6 100644 --- a/AUTHORS +++ b/AUTHORS @@ -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) diff --git a/scripts/get_native_properties.sh b/scripts/get_native_properties.sh index 773d6c271..e8c8f23f2 100755 --- a/scripts/get_native_properties.sh +++ b/scripts/get_native_properties.sh @@ -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' diff --git a/src/Makefile b/src/Makefile index 14c3c50c9..40dceae0b 100644 --- a/src/Makefile +++ b/src/Makefile @@ -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") && \ diff --git a/src/misc.cpp b/src/misc.cpp index f85356c59..3bdde000f 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -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 diff --git a/src/movegen.cpp b/src/movegen.cpp index a73bd8501..10adc70be 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -24,12 +24,88 @@ #include "bitboard.h" #include "position.h" +#if defined(USE_AVX512ICL) + #include + #include + #include +#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 +inline Move* splat_pawn_moves(Move* moveList, Bitboard to_bb) { + alignas(64) static constexpr auto SPLAT_TABLE = [] { + std::array table{}; + for (int8_t i = 0; i < 64; i++) + { + Square from{std::clamp(i - offset, 0, 63)}; + table[i] = {Move(from, Square{i})}; + } + return table; + }(); + + auto table = reinterpret_cast(SPLAT_TABLE.data()); + + moveList = + write_moves(moveList, static_cast(to_bb >> 0), _mm512_load_si512(table + 0)); + moveList = + write_moves(moveList, static_cast(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 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(SPLAT_TABLE.data()); + + moveList = write_moves(moveList, static_cast(to_bb >> 0), + _mm512_or_si512(_mm512_load_si512(table + 0), fromVec)); + moveList = write_moves(moveList, static_cast(to_bb >> 32), + _mm512_or_si512(_mm512_load_si512(table + 1), fromVec)); + + return moveList; +} + +#else + +template +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 -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 -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(moveList, b1); + moveList = splat_pawn_moves(moveList, b2); } // Promotions and underpromotions @@ -114,17 +181,8 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta Bitboard b1 = shift(pawnsNotOn7) & enemies; Bitboard b2 = shift(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(moveList, b1); + moveList = splat_pawn_moves(moveList, b2); if (pos.ep_square() != SQ_NONE) { @@ -148,7 +206,7 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta template -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(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 -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(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 -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(const Position&, ExtMove*); -template ExtMove* generate(const Position&, ExtMove*); -template ExtMove* generate(const Position&, ExtMove*); -template ExtMove* generate(const Position&, ExtMove*); - +template Move* generate(const Position&, Move*); +template Move* generate(const Position&, Move*); +template Move* generate(const Position&, Move*); +template Move* generate(const Position&, Move*); // generate generates all the legal moves in the given position template<> -ExtMove* generate(const Position& pos, ExtMove* moveList) { +Move* generate(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(us); - ExtMove* cur = moveList; + Move* cur = moveList; moveList = pos.checkers() ? generate(pos, moveList) : generate(pos, moveList); diff --git a/src/movegen.h b/src/movegen.h index 7c6cceb7c..287fd8927 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -49,7 +49,7 @@ struct ExtMove: public Move { inline bool operator<(const ExtMove& f, const ExtMove& s) { return f.value < s.value; } template -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(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 diff --git a/src/movepick.cpp b/src/movepick.cpp index 3e7c10160..79b6f55a2 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -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 -void MovePicker::score() { +ExtMove* MovePicker::score(MoveList& ml) { static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); @@ -138,8 +138,12 @@ void MovePicker::score() { threatByLesser[QUEEN] = pos.attacks_by(~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 ml(pos); + cur = endBadCaptures = moves; - endCur = endCaptures = generate(pos, cur); + endCur = endCaptures = score(ml); - score(); partial_insertion_sort(cur, endCur, std::numeric_limits::min()); ++stage; goto top; + } case GOOD_CAPTURE : if (select([&]() { @@ -246,9 +253,10 @@ top: case QUIET_INIT : if (!skipQuiets) { - endCur = endGenerated = generate(pos, cur); + MoveList ml(pos); + + endCur = endGenerated = score(ml); - score(); partial_insertion_sort(cur, endCur, -3560 * depth); } @@ -283,14 +291,16 @@ top: return Move::none(); - case EVASION_INIT : + case EVASION_INIT : { + MoveList ml(pos); + cur = moves; - endCur = endGenerated = generate(pos, cur); + endCur = endGenerated = score(ml); - score(); partial_insertion_sort(cur, endCur, std::numeric_limits::min()); ++stage; [[fallthrough]]; + } case EVASION : case QCAPTURE : diff --git a/src/movepick.h b/src/movepick.h index bf0c96c7c..9d6c02b0e 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -55,8 +55,8 @@ class MovePicker { private: template Move select(Pred); - template - void score(); + template + ExtMove* score(MoveList&); ExtMove* begin() { return cur; } ExtMove* end() { return endCur; }