Improve update_piece_threats

passed on avx512ICL:
LLR: 2.95 (-2.94,2.94) <0.00,2.00>
Total: 30240 W: 8026 L: 7726 D: 14488
Ptnml(0-2): 95, 3235, 8171, 3513, 106
https://tests.stockfishchess.org/tests/view/69281d9ab23dfeae38cfeeb8

passed on generic architectures:
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 73184 W: 19183 L: 18821 D: 35180
Ptnml(0-2): 258, 7988, 19744, 8338, 264
https://tests.stockfishchess.org/tests/view/6928ba11b23dfeae38cff276

subsequent cleanups tested as:
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 72480 W: 18678 L: 18502 D: 35300
Ptnml(0-2): 242, 7925, 19718, 8125, 230
https://tests.stockfishchess.org/tests/view/692a26adb23dfeae38cff566

We add an argument noRaysContaining, which skips all discoveries which contain
all bits in the argument; if the argument is from | to, then this will
eliminate the discovery. Separately, on AVX512ICL we can speed up the
computation of DirtyThreats by moving from a pop_lsb loop to a vector
extraction with vpcompressb. See PR for details.

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

No functional change
This commit is contained in:
Timothy Herchen
2025-12-01 17:26:50 +01:00
committed by Joost VandeVondele
parent 9e2ee13e77
commit abd835dcbc
4 changed files with 109 additions and 24 deletions
+7
View File
@@ -142,6 +142,13 @@ class ValueList {
const T* end() const { return values_ + size_; } const T* end() const { return values_ + size_; }
const T& operator[](int index) const { return values_[index]; } const T& operator[](int index) const { return values_[index]; }
T* make_space(size_t count) {
T* result = &values_[size_];
size_ += count;
assert(size_ <= MaxSize);
return result;
}
private: private:
T values_[MaxSize]; T values_[MaxSize];
std::size_t size_ = 0; std::size_t size_ = 0;
+80 -14
View File
@@ -1052,8 +1052,51 @@ inline void add_dirty_threat(
dts->list.push_back({pc, threatened, s, threatenedSq, PutPiece}); dts->list.push_back({pc, threatened, s, threatenedSq, PutPiece});
} }
#ifdef USE_AVX512ICL
// Given a DirtyThreat template and bit offsets to insert the piece type and square, write the threats
// present at the given bitboard.
template<int SqShift, int PcShift>
void write_multiple_dirties(const Position& p,
Bitboard mask,
DirtyThreat dt_template,
DirtyThreats* dts) {
static_assert(sizeof(DirtyThreat) == 4);
const __m512i board = _mm512_loadu_si512(p.piece_array().data());
const __m512i AllSquares = _mm512_set_epi8(
63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18,
17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
const int dt_count = popcount(mask);
assert(dt_count <= 16);
const __m512i template_v = _mm512_set1_epi32(dt_template.raw());
auto* write = dts->list.make_space(dt_count);
// Extract the list of squares and upconvert to 32 bits. There are never more than 16
// incoming threats so this is sufficient.
__m512i threat_squares = _mm512_maskz_compress_epi8(mask, AllSquares);
threat_squares = _mm512_cvtepi8_epi32(_mm512_castsi512_si128(threat_squares));
__m512i threat_pieces =
_mm512_maskz_permutexvar_epi8(0x1111111111111111ULL, threat_squares, board);
// Shift the piece and square into place
threat_squares = _mm512_slli_epi32(threat_squares, SqShift);
threat_pieces = _mm512_slli_epi32(threat_pieces, PcShift);
const __m512i dirties =
_mm512_ternarylogic_epi32(template_v, threat_squares, threat_pieces, 254 /* A | B | C */);
_mm512_storeu_si512(reinterpret_cast<__m512i*>(write), dirties);
}
#endif
template<bool PutPiece, bool ComputeRay> template<bool PutPiece, bool ComputeRay>
void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) { void Position::update_piece_threats(Piece pc,
Square s,
DirtyThreats* const dts,
Bitboard noRaysContaining) {
const Bitboard occupied = pieces(); const Bitboard occupied = pieces();
const Bitboard rookQueens = pieces(ROOK, QUEEN); const Bitboard rookQueens = pieces(ROOK, QUEEN);
const Bitboard bishopQueens = pieces(BISHOP, QUEEN); const Bitboard bishopQueens = pieces(BISHOP, QUEEN);
@@ -1093,7 +1136,36 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts)
} }
threatened &= occupied; threatened &= occupied;
Bitboard sliders = (rookQueens & rAttacks) | (bishopQueens & bAttacks);
Bitboard incoming_threats =
(PseudoAttacks[KNIGHT][s] & knights) | (attacks_bb<PAWN>(s, WHITE) & blackPawns)
| (attacks_bb<PAWN>(s, BLACK) & whitePawns) | (PseudoAttacks[KING][s] & kings);
#ifdef USE_AVX512ICL
if (threatened)
{
if constexpr (PutPiece)
{
dts->threatenedSqs |= threatened;
dts->threateningSqs |= square_bb(s);
}
DirtyThreat dt_template{pc, NO_PIECE, s, Square(0), PutPiece};
write_multiple_dirties<DirtyThreat::ThreatenedSqOffset, DirtyThreat::ThreatenedPcOffset>(
*this, threatened, dt_template, dts);
}
Bitboard all_attackers = sliders | incoming_threats;
if (!all_attackers)
return; // Square s is threatened iff there's at least one attacker
dts->threatenedSqs |= square_bb(s);
dts->threateningSqs |= all_attackers;
DirtyThreat dt_template{NO_PIECE, pc, Square(0), s, PutPiece};
write_multiple_dirties<DirtyThreat::PcSqOffset, DirtyThreat::PcOffset>(*this, all_attackers,
dt_template, dts);
#else
while (threatened) while (threatened)
{ {
Square threatenedSq = pop_lsb(threatened); Square threatenedSq = pop_lsb(threatened);
@@ -1104,8 +1176,7 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts)
add_dirty_threat<PutPiece>(dts, pc, threatenedPc, s, threatenedSq); add_dirty_threat<PutPiece>(dts, pc, threatenedPc, s, threatenedSq);
} }
#endif
Bitboard sliders = (rookQueens & rAttacks) | (bishopQueens & bAttacks);
if constexpr (ComputeRay) if constexpr (ComputeRay)
{ {
@@ -1118,30 +1189,24 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts)
const Bitboard discovered = ray & qAttacks & occupied; const Bitboard discovered = ray & qAttacks & occupied;
assert(!more_than_one(discovered)); assert(!more_than_one(discovered));
if (discovered) if (discovered && (RayPassBB[sliderSq][s] & noRaysContaining) != noRaysContaining)
{ {
const Square threatenedSq = lsb(discovered); const Square threatenedSq = lsb(discovered);
const Piece threatenedPc = piece_on(threatenedSq); const Piece threatenedPc = piece_on(threatenedSq);
add_dirty_threat<!PutPiece>(dts, slider, threatenedPc, sliderSq, threatenedSq); add_dirty_threat<!PutPiece>(dts, slider, threatenedPc, sliderSq, threatenedSq);
} }
#ifndef USE_AVX512ICL // for ICL, direct threats were processed earlier (all_attackers)
add_dirty_threat<PutPiece>(dts, slider, pc, sliderSq, s); add_dirty_threat<PutPiece>(dts, slider, pc, sliderSq, s);
#endif
} }
} }
else else
{ {
while (sliders) incoming_threats |= sliders;
{
Square sliderSq = pop_lsb(sliders);
Piece slider = piece_on(sliderSq);
add_dirty_threat<PutPiece>(dts, slider, pc, sliderSq, s);
}
} }
Bitboard incoming_threats = #ifndef USE_AVX512ICL
(PseudoAttacks[KNIGHT][s] & knights) | (attacks_bb<PAWN>(s, WHITE) & blackPawns)
| (attacks_bb<PAWN>(s, BLACK) & whitePawns) | (PseudoAttacks[KING][s] & kings);
while (incoming_threats) while (incoming_threats)
{ {
Square srcSq = pop_lsb(incoming_threats); Square srcSq = pop_lsb(incoming_threats);
@@ -1152,6 +1217,7 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts)
add_dirty_threat<PutPiece>(dts, srcPc, pc, srcSq, s); add_dirty_threat<PutPiece>(dts, srcPc, pc, srcSq, s);
} }
#endif
} }
// Helper used to do/undo a castling move. This is a bit // Helper used to do/undo a castling move. This is a bit
+6 -3
View File
@@ -187,7 +187,10 @@ class Position {
// Other helpers // Other helpers
template<bool PutPiece, bool ComputeRay = true> template<bool PutPiece, bool ComputeRay = true>
void update_piece_threats(Piece pc, Square s, DirtyThreats* const dts); void update_piece_threats(Piece pc,
Square s,
DirtyThreats* const dts,
Bitboard noRaysContaining = -1ULL);
void move_piece(Square from, Square to, DirtyThreats* const dts = nullptr); void move_piece(Square from, Square to, DirtyThreats* const dts = nullptr);
template<bool Do> template<bool Do>
void do_castling(Color us, void do_castling(Color us,
@@ -372,7 +375,7 @@ inline void Position::move_piece(Square from, Square to, DirtyThreats* const dts
Bitboard fromTo = from | to; Bitboard fromTo = from | to;
if (dts) if (dts)
update_piece_threats<false>(pc, from, dts); update_piece_threats<false>(pc, from, dts, fromTo);
byTypeBB[ALL_PIECES] ^= fromTo; byTypeBB[ALL_PIECES] ^= fromTo;
byTypeBB[type_of(pc)] ^= fromTo; byTypeBB[type_of(pc)] ^= fromTo;
@@ -381,7 +384,7 @@ inline void Position::move_piece(Square from, Square to, DirtyThreats* const dts
board[to] = pc; board[to] = pc;
if (dts) if (dts)
update_piece_threats<true>(pc, to, dts); update_piece_threats<true>(pc, to, dts, fromTo);
} }
inline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) { inline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) {
+11 -2
View File
@@ -293,9 +293,17 @@ struct DirtyPiece {
// Keep track of what threats change on the board (used by NNUE) // Keep track of what threats change on the board (used by NNUE)
struct DirtyThreat { struct DirtyThreat {
static constexpr int PcSqOffset = 0;
static constexpr int ThreatenedSqOffset = 8;
static constexpr int ThreatenedPcOffset = 16;
static constexpr int PcOffset = 20;
DirtyThreat() { /* don't initialize data */ } DirtyThreat() { /* don't initialize data */ }
DirtyThreat(uint32_t raw) :
data(raw) {}
DirtyThreat(Piece pc, Piece threatened_pc, Square pc_sq, Square threatened_sq, bool add) { DirtyThreat(Piece pc, Piece threatened_pc, Square pc_sq, Square threatened_sq, bool add) {
data = (add << 31) | (pc << 20) | (threatened_pc << 16) | (threatened_sq << 8) | (pc_sq); data = (uint32_t(add) << 31) | (pc << PcOffset) | (threatened_pc << ThreatenedPcOffset)
| (threatened_sq << ThreatenedSqOffset) | (pc_sq << PcSqOffset);
} }
Piece pc() const { return static_cast<Piece>(data >> 20 & 0xf); } Piece pc() const { return static_cast<Piece>(data >> 20 & 0xf); }
@@ -303,12 +311,13 @@ struct DirtyThreat {
Square threatened_sq() const { return static_cast<Square>(data >> 8 & 0xff); } Square threatened_sq() const { return static_cast<Square>(data >> 8 & 0xff); }
Square pc_sq() const { return static_cast<Square>(data & 0xff); } Square pc_sq() const { return static_cast<Square>(data & 0xff); }
bool add() const { return data >> 31; } bool add() const { return data >> 31; }
uint32_t raw() const { return data; }
private: private:
uint32_t data; uint32_t data;
}; };
using DirtyThreatList = ValueList<DirtyThreat, 80>; using DirtyThreatList = ValueList<DirtyThreat, 96>;
// A piece can be involved in at most 8 outgoing attacks and 16 incoming attacks. // A piece can be involved in at most 8 outgoing attacks and 16 incoming attacks.
// Moving a piece also can reveal at most 8 discovered attacks. // Moving a piece also can reveal at most 8 discovered attacks.