Introduce double incremental accumulator updates

when we need to update an accumulator by two moves and the second move
captures the piece moved in the first move, we can skip computing the
middle accumulator and cancel a feature add with a feature remove to
save work.

Passed STC
https://tests.stockfishchess.org/tests/view/67f70b1c31d7cf8afdc45f51
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 72800 W: 18878 L: 18529 D: 35393
Ptnml(0-2): 160, 7711, 20374, 7930, 225

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

No functional change
This commit is contained in:
Carlos Esparza
2025-04-09 16:09:47 -07:00
committed by Disservin
parent 4e49f8dff9
commit fda269a299

View File

@@ -46,6 +46,13 @@ namespace Stockfish::Eval::NNUE {
namespace {
template<Color Perspective, IndexType TransformedFeatureDimensions>
void double_inc_update(const FeatureTransformer<TransformedFeatureDimensions>& featureTransformer,
const Square ksq,
AccumulatorState& middle_state,
AccumulatorState& target_state,
const AccumulatorState& computed);
template<Color Perspective, bool Forward, IndexType TransformedFeatureDimensions>
void update_accumulator_incremental(
const FeatureTransformer<TransformedFeatureDimensions>& featureTransformer,
@@ -142,8 +149,27 @@ void AccumulatorStack::forward_update_incremental(
const Square ksq = pos.square<KING>(Perspective);
for (std::size_t next = begin + 1; next < size; next++)
{
if (next + 1 < size)
{
auto& dp1 = accumulators[next].dirtyPiece;
auto& dp2 = accumulators[next + 1].dirtyPiece;
if (dp2.dirty_num >= 2 && dp1.piece[0] == dp2.piece[1] && dp1.to[0] == dp2.from[1])
{
const Square captureSq = dp1.to[0];
dp1.to[0] = dp2.from[1] = SQ_NONE;
double_inc_update<Perspective>(featureTransformer, ksq, accumulators[next],
accumulators[next + 1], accumulators[next - 1]);
dp1.to[0] = dp2.from[1] = captureSq;
next++;
continue;
}
}
update_accumulator_incremental<Perspective, true>(
featureTransformer, ksq, accumulators[next], accumulators[next - 1]);
}
assert((latest().acc<Dimensions>()).computed[Perspective]);
}
@@ -240,6 +266,49 @@ auto make_accumulator_update_context(const FeatureTransformer<Dimensions>& featu
accumulatorTo};
}
template<Color Perspective, IndexType TransformedFeatureDimensions>
void double_inc_update(const FeatureTransformer<TransformedFeatureDimensions>& featureTransformer,
const Square ksq,
AccumulatorState& middle_state,
AccumulatorState& target_state,
const AccumulatorState& computed) {
assert(computed.acc<TransformedFeatureDimensions>().computed[Perspective]);
assert(!middle_state.acc<TransformedFeatureDimensions>().computed[Perspective]);
assert(!target_state.acc<TransformedFeatureDimensions>().computed[Perspective]);
FeatureSet::IndexList removed, added;
FeatureSet::append_changed_indices<Perspective>(ksq, middle_state.dirtyPiece, removed, added);
// you can't capture a piece that was just involved in castling since the rook ends up
// in a square that the king passed
assert(added.size() < 2);
FeatureSet::append_changed_indices<Perspective>(ksq, target_state.dirtyPiece, removed, added);
assert(added.size() == 1);
assert(removed.size() == 2 || removed.size() == 3);
// Workaround compiler warning for uninitialized variables, replicated on
// profile builds on windows with gcc 14.2.0.
// TODO remove once unneeded
sf_assume(added.size() == 1);
sf_assume(removed.size() == 2 || removed.size() == 3);
auto updateContext =
make_accumulator_update_context<Perspective>(featureTransformer, computed, target_state);
if (removed.size() == 2)
{
updateContext.template apply<Add, Sub, Sub>(added[0], removed[0], removed[1]);
}
else
{
updateContext.template apply<Add, Sub, Sub, Sub>(added[0], removed[0], removed[1],
removed[2]);
}
target_state.acc<TransformedFeatureDimensions>().computed[Perspective] = true;
}
template<Color Perspective, bool Forward, IndexType TransformedFeatureDimensions>
void update_accumulator_incremental(
const FeatureTransformer<TransformedFeatureDimensions>& featureTransformer,