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
+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);