Don't copy around DirtyThreats

The contents of DirtyThreats gets memcpyed twice for each call to do_move.
(Return value optimization doesn't apply to the do_move function itself because
it constructs a std::pair, so it gets copied; and the calls to reset also
require a copy.) This patch inserts the dirty info in place.

Sometimes the caller of do_move ignores the DirtyThreats info, so we pass in
scratch objects. I found that stack-allocating these scratch objects was bad on
Fishtest, so I begrudgingly put them in the Position struct. Both templating
the do_move function on whether dirty threats are needed and putting a
null-check branch for each use of dirty threats were slowdowns locally. Of
course, nothing prevents a future attempt at cleaning this up.

passed STC
LLR: 2.96 (-2.94,2.94) <0.00,2.00>
Total: 68448 W: 17770 L: 17418 D: 33260
Ptnml(0-2): 198, 7425, 18630, 7769, 202
https://tests.stockfishchess.org/tests/view/6914c01a7ca87818523318ba

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

No functional change
This commit is contained in:
Timothy Herchen
2025-11-13 22:17:03 +01:00
committed by Joost VandeVondele
parent 3ae7684714
commit fa4f05d3ef
5 changed files with 55 additions and 37 deletions
+6 -3
View File
@@ -21,6 +21,7 @@
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
#include <cstdint> #include <cstdint>
#include <new>
#include <type_traits> #include <type_traits>
#include "../bitboard.h" #include "../bitboard.h"
@@ -122,11 +123,13 @@ void AccumulatorStack::reset() noexcept {
size = 1; size = 1;
} }
void AccumulatorStack::push(const DirtyBoardData& dirtyBoardData) noexcept { std::pair<DirtyPiece&, DirtyThreats&> AccumulatorStack::push() noexcept {
assert(size < MaxSize); assert(size < MaxSize);
psq_accumulators[size].reset(dirtyBoardData.dp); auto& dp = psq_accumulators[size].reset();
threat_accumulators[size].reset(dirtyBoardData.dts); auto& dts = threat_accumulators[size].reset();
new (&dts) DirtyThreats;
size++; size++;
return {dp, dts};
} }
void AccumulatorStack::pop() noexcept { void AccumulatorStack::pop() noexcept {
+8
View File
@@ -25,6 +25,7 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <utility>
#include "../types.h" #include "../types.h"
#include "nnue_architecture.h" #include "nnue_architecture.h"
@@ -141,6 +142,12 @@ struct AccumulatorState {
accumulatorBig.computed.fill(false); accumulatorBig.computed.fill(false);
accumulatorSmall.computed.fill(false); accumulatorSmall.computed.fill(false);
} }
typename FeatureSet::DiffType& reset() noexcept {
accumulatorBig.computed.fill(false);
accumulatorSmall.computed.fill(false);
return diff;
}
}; };
class AccumulatorStack { class AccumulatorStack {
@@ -152,6 +159,7 @@ class AccumulatorStack {
void reset() noexcept; void reset() noexcept;
void push(const DirtyBoardData& dirtyBoardData) noexcept; void push(const DirtyBoardData& dirtyBoardData) noexcept;
std::pair<DirtyPiece&, DirtyThreats&> push() noexcept;
void pop() noexcept; void pop() noexcept;
template<IndexType Dimensions> template<IndexType Dimensions>
+4 -6
View File
@@ -201,7 +201,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si) {
Square sq = SQ_A8; Square sq = SQ_A8;
std::istringstream ss(fenStr); std::istringstream ss(fenStr);
std::memset(this, 0, sizeof(Position)); std::memset(reinterpret_cast<char*>(this), 0, sizeof(Position));
std::memset(si, 0, sizeof(StateInfo)); std::memset(si, 0, sizeof(StateInfo));
st = si; st = si;
@@ -688,9 +688,11 @@ bool Position::gives_check(Move m) const {
// moves should be filtered out before this function is called. // moves should be filtered out before this function is called.
// If a pointer to the TT table is passed, the entry for the new position // If a pointer to the TT table is passed, the entry for the new position
// will be prefetched // will be prefetched
DirtyBoardData Position::do_move(Move m, void Position::do_move(Move m,
StateInfo& newSt, StateInfo& newSt,
bool givesCheck, bool givesCheck,
DirtyPiece& dp,
DirtyThreats& dts,
const TranspositionTable* tt = nullptr) { const TranspositionTable* tt = nullptr) {
assert(m.is_ok()); assert(m.is_ok());
@@ -720,12 +722,10 @@ DirtyBoardData Position::do_move(Move m,
bool checkEP = false; bool checkEP = false;
DirtyPiece dp;
dp.pc = pc; dp.pc = pc;
dp.from = from; dp.from = from;
dp.to = to; dp.to = to;
dp.add_sq = SQ_NONE; dp.add_sq = SQ_NONE;
DirtyThreats dts;
dts.us = us; dts.us = us;
dts.prevKsq = square<KING>(us); dts.prevKsq = square<KING>(us);
dts.threatenedSqs = dts.threateningSqs = 0; dts.threatenedSqs = dts.threateningSqs = 0;
@@ -970,8 +970,6 @@ DirtyBoardData Position::do_move(Move m,
assert(!(bool(captured) || m.type_of() == CASTLING) ^ (dp.remove_sq != SQ_NONE)); assert(!(bool(captured) || m.type_of() == CASTLING) ^ (dp.remove_sq != SQ_NONE));
assert(dp.from != SQ_NONE); assert(dp.from != SQ_NONE);
assert(!(dp.add_sq != SQ_NONE) ^ (m.type_of() == PROMOTION || m.type_of() == CASTLING)); assert(!(dp.add_sq != SQ_NONE) ^ (m.type_of() == PROMOTION || m.type_of() == CASTLING));
return {dp, dts};
} }
+11 -2
View File
@@ -24,6 +24,7 @@
#include <deque> #include <deque>
#include <iosfwd> #include <iosfwd>
#include <memory> #include <memory>
#include <new>
#include <string> #include <string>
#include "bitboard.h" #include "bitboard.h"
@@ -134,7 +135,12 @@ class Position {
// Doing and undoing moves // Doing and undoing moves
void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt); void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt);
DirtyBoardData do_move(Move m, StateInfo& newSt, bool givesCheck, const TranspositionTable* tt); void do_move(Move m,
StateInfo& newSt,
bool givesCheck,
DirtyPiece& dp,
DirtyThreats& dts,
const TranspositionTable* tt);
void undo_move(Move m); void undo_move(Move m);
void do_null_move(StateInfo& newSt, const TranspositionTable& tt); void do_null_move(StateInfo& newSt, const TranspositionTable& tt);
void undo_null_move(); void undo_null_move();
@@ -204,6 +210,8 @@ class Position {
int gamePly; int gamePly;
Color sideToMove; Color sideToMove;
bool chess960; bool chess960;
DirtyPiece scratch_dp;
DirtyThreats scratch_dts;
}; };
std::ostream& operator<<(std::ostream& os, const Position& pos); std::ostream& operator<<(std::ostream& os, const Position& pos);
@@ -389,7 +397,8 @@ inline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) {
} }
inline void Position::do_move(Move m, StateInfo& newSt, const TranspositionTable* tt = nullptr) { inline void Position::do_move(Move m, StateInfo& newSt, const TranspositionTable* tt = nullptr) {
do_move(m, newSt, gives_check(m), tt); new (&scratch_dts) DirtyThreats;
do_move(m, newSt, gives_check(m), scratch_dp, scratch_dts, tt);
} }
inline StateInfo* Position::state() const { return st; } inline StateInfo* Position::state() const { return st; }
+4 -4
View File
@@ -531,16 +531,16 @@ void Search::Worker::do_move(
bool capture = pos.capture_stage(move); bool capture = pos.capture_stage(move);
nodes.fetch_add(1, std::memory_order_relaxed); nodes.fetch_add(1, std::memory_order_relaxed);
DirtyBoardData dirtyBoardData = pos.do_move(move, st, givesCheck, &tt); auto [dirtyPiece, dirtyThreats] = accumulatorStack.push();
accumulatorStack.push(dirtyBoardData); pos.do_move(move, st, givesCheck, dirtyPiece, dirtyThreats, &tt);
if (ss != nullptr) if (ss != nullptr)
{ {
ss->currentMove = move; ss->currentMove = move;
ss->continuationHistory = ss->continuationHistory =
&continuationHistory[ss->inCheck][capture][dirtyBoardData.dp.pc][move.to_sq()]; &continuationHistory[ss->inCheck][capture][dirtyPiece.pc][move.to_sq()];
ss->continuationCorrectionHistory = ss->continuationCorrectionHistory =
&continuationCorrectionHistory[dirtyBoardData.dp.pc][move.to_sq()]; &continuationCorrectionHistory[dirtyPiece.pc][move.to_sq()];
} }
} }