Improve index generation

The speedup seems to vary by machine.  The indexing function can be changed w/o needing to understand intrinsics.

Result of 100 runs
==================
base (...ish_baseline) =    1719637  +/- 3233
test (./stockfish    ) =    1734245  +/- 3534
diff                   =     +14608  +/- 4868

speedup        = +0.0085
P(speedup > 0) =  1.0000

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

No functional change
This commit is contained in:
Timothy Herchen
2025-11-01 10:42:42 +01:00
committed by Joost VandeVondele
parent 3bb01ce7a9
commit 6764561916
3 changed files with 48 additions and 33 deletions
+36 -24
View File
@@ -18,9 +18,9 @@
#include "nnue_accumulator.h" #include "nnue_accumulator.h"
#include <algorithm>
#include <cassert> #include <cassert>
#include <cstdint> #include <cstdint>
#include <initializer_list>
#include <type_traits> #include <type_traits>
#include "../bitboard.h" #include "../bitboard.h"
@@ -362,6 +362,29 @@ void update_accumulator_incremental(
(target_state.acc<TransformedFeatureDimensions>()).computed[Perspective] = true; (target_state.acc<TransformedFeatureDimensions>()).computed[Perspective] = true;
} }
Bitboard get_changed_pieces(const Piece old[SQUARE_NB], const Piece new_[SQUARE_NB]) {
#if defined(USE_AVX512) || defined(USE_AVX2)
static_assert(sizeof(Piece) == 1);
Bitboard same_bb = 0;
for (int i = 0; i < 64; i += 32)
{
const __m256i old_v = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(old + i));
const __m256i new_v = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(new_ + i));
const __m256i cmp_equal = _mm256_cmpeq_epi8(old_v, new_v);
const std::uint32_t equal_mask = _mm256_movemask_epi8(cmp_equal);
same_bb |= static_cast<Bitboard>(equal_mask) << i;
}
return ~same_bb;
#else
Bitboard changed = 0;
for (Square sq = SQUARE_ZERO; sq < SQUARE_NB; ++sq)
{
changed |= static_cast<Bitboard>(old[sq] != new_[sq]) << sq;
}
return changed;
#endif
}
template<Color Perspective, IndexType Dimensions> template<Color Perspective, IndexType Dimensions>
void update_accumulator_refresh_cache(const FeatureTransformer<Dimensions>& featureTransformer, void update_accumulator_refresh_cache(const FeatureTransformer<Dimensions>& featureTransformer,
const Position& pos, const Position& pos,
@@ -374,29 +397,24 @@ void update_accumulator_refresh_cache(const FeatureTransformer<Dimensions>& feat
auto& entry = cache[ksq][Perspective]; auto& entry = cache[ksq][Perspective];
FeatureSet::IndexList removed, added; FeatureSet::IndexList removed, added;
for (Color c : {WHITE, BLACK}) const Bitboard changed_bb = get_changed_pieces(entry.pieces, pos.piece_array());
{ Bitboard removed_bb = changed_bb & entry.pieceBB;
for (PieceType pt = PAWN; pt <= KING; ++pt) Bitboard added_bb = changed_bb & pos.pieces();
{
const Piece piece = make_piece(c, pt);
const Bitboard oldBB = entry.byColorBB[c] & entry.byTypeBB[pt];
const Bitboard newBB = pos.pieces(c, pt);
Bitboard toRemove = oldBB & ~newBB;
Bitboard toAdd = newBB & ~oldBB;
while (toRemove) while (removed_bb)
{ {
Square sq = pop_lsb(toRemove); Square sq = pop_lsb(removed_bb);
removed.push_back(FeatureSet::make_index<Perspective>(sq, piece, ksq)); removed.push_back(FeatureSet::make_index<Perspective>(sq, entry.pieces[sq], ksq));
} }
while (toAdd) while (added_bb)
{ {
Square sq = pop_lsb(toAdd); Square sq = pop_lsb(added_bb);
added.push_back(FeatureSet::make_index<Perspective>(sq, piece, ksq)); added.push_back(FeatureSet::make_index<Perspective>(sq, pos.piece_on(sq), ksq));
}
}
} }
entry.pieceBB = pos.pieces();
std::copy_n(pos.piece_array(), SQUARE_NB, entry.pieces);
auto& accumulator = accumulatorState.acc<Dimensions>(); auto& accumulator = accumulatorState.acc<Dimensions>();
accumulator.computed[Perspective] = true; accumulator.computed[Perspective] = true;
@@ -518,12 +536,6 @@ void update_accumulator_refresh_cache(const FeatureTransformer<Dimensions>& feat
std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation, std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation,
sizeof(int32_t) * PSQTBuckets); sizeof(int32_t) * PSQTBuckets);
#endif #endif
for (Color c : {WHITE, BLACK})
entry.byColorBB[c] = pos.pieces(c);
for (PieceType pt = PAWN; pt <= KING; ++pt)
entry.byTypeBB[pt] = pos.pieces(pt);
} }
} }
+2 -2
View File
@@ -71,8 +71,8 @@ struct AccumulatorCaches {
struct alignas(CacheLineSize) Entry { struct alignas(CacheLineSize) Entry {
BiasType accumulation[Size]; BiasType accumulation[Size];
PSQTWeightType psqtAccumulation[PSQTBuckets]; PSQTWeightType psqtAccumulation[PSQTBuckets];
Bitboard byColorBB[COLOR_NB]; Piece pieces[SQUARE_NB];
Bitboard byTypeBB[PIECE_TYPE_NB]; Bitboard pieceBB;
// To initialize a refresh entry, we set all its bitboards empty, // To initialize a refresh entry, we set all its bitboards empty,
// so we put the biases in the accumulation, without any weights on top // so we put the biases in the accumulation, without any weights on top
+3
View File
@@ -93,6 +93,7 @@ class Position {
template<typename... PieceTypes> template<typename... PieceTypes>
Bitboard pieces(Color c, PieceTypes... pts) const; Bitboard pieces(Color c, PieceTypes... pts) const;
Piece piece_on(Square s) const; Piece piece_on(Square s) const;
const Piece* piece_array() const;
Square ep_square() const; Square ep_square() const;
bool empty(Square s) const; bool empty(Square s) const;
template<PieceType Pt> template<PieceType Pt>
@@ -208,6 +209,8 @@ inline Piece Position::piece_on(Square s) const {
return board[s]; return board[s];
} }
inline const Piece* Position::piece_array() const { return board; }
inline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; } inline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; }
inline Piece Position::moved_piece(Move m) const { return piece_on(m.from_sq()); } inline Piece Position::moved_piece(Move m) const { return piece_on(m.from_sq()); }