Merge commit 'c8213ba0d047569141ed58f5eb86579d976b5614' into cluster

As part of this work, we needed to rename the namespace Stockfish::Cluster
to Stockfish::Distributed, as Cluster is now a type name.
This commit is contained in:
Steinar H. Gunderson
2025-12-25 00:17:08 +01:00
15 changed files with 355 additions and 292 deletions
+10 -12
View File
@@ -35,7 +35,7 @@
#include "search.h"
namespace Stockfish {
namespace Cluster {
namespace Distributed {
// Total number of ranks and rank within the communicator
static int world_rank = MPI_PROC_NULL;
@@ -297,7 +297,7 @@ void cluster_info(const ThreadPool& threads, Depth depth, TimePoint elapsed) {
void save(TranspositionTable& TT,
ThreadPool& threads,
Search::Worker* thread,
TTEntry* tte,
TTWriter ttWriter,
Key k,
Value v,
bool PvHit,
@@ -308,7 +308,7 @@ void save(TranspositionTable& TT,
uint8_t generation8) {
// Standard save to the TT
tte->save(k, v, PvHit, b, d, m, ev, generation8);
ttWriter.write(k, v, PvHit, b, d, m, ev, generation8);
// If the entry is of sufficient depth to be worth communicating, take action.
if (d > 3)
@@ -321,7 +321,7 @@ void save(TranspositionTable& TT,
// prepares the send buffer.
{
std::lock_guard<std::mutex> lk(thread->ttCache.mutex);
thread->ttCache.buffer.replace(KeyedTTEntry(k, *tte));
thread->ttCache.buffer.replace(KeyedTTEntry(k, TTData(m, v, ev, d, b, PvHit)));
++TTCacheCounter;
}
@@ -364,13 +364,11 @@ void save(TranspositionTable& TT,
for (size_t i = irank * recvBuffPerRankSize;
i < (irank + 1) * recvBuffPerRankSize; ++i)
{
auto&& e = TTSendRecvBuffs[sendRecvPosted % 2][i];
bool found;
TTEntry* replace_tte;
replace_tte = TT.probe(e.first, found);
replace_tte->save(e.first, e.second.value(), e.second.is_pv(),
e.second.bound(), e.second.depth(), e.second.move(),
e.second.eval(), TT.generation());
auto&& e = TTSendRecvBuffs[sendRecvPosted % 2][i];
auto [ttHit, ttData, ttWriterForRecvd] = TT.probe(e.first);
ttWriterForRecvd.write(e.first, e.second.value, e.second.is_pv,
e.second.bound, e.second.depth, e.second.move,
e.second.eval, TT.generation());
}
}
@@ -478,7 +476,7 @@ uint64_t TT_saves(const ThreadPool& threads) { return TTsavesOthers + threads.TT
#include "thread.h"
namespace Stockfish {
namespace Cluster {
namespace Distributed {
uint64_t nodes_searched(const ThreadPool& threads) { return threads.nodes_searched(); }
+9 -8
View File
@@ -24,6 +24,7 @@
#include <istream>
#include <string>
#include "misc.h"
#include "tt.h"
namespace Stockfish {
@@ -34,7 +35,7 @@ namespace Search {
class Worker;
}
/// The Cluster namespace contains functionality required to run on distributed
/// The Distributed namespace contains functionality required to run on distributed
/// memory architectures using MPI as the message passing interface. On a high level,
/// a 'lazy SMP'-like scheme is implemented where TT saves of sufficient depth are
/// collected on each rank and distributed to, and used by, all other ranks,
@@ -44,7 +45,7 @@ class Worker;
/// quantities per MPI rank. It is recommended to have one rank (MPI process) per node.
/// For the non-MPI case, wrappers that will be compiler-optimized away are provided.
namespace Cluster {
namespace Distributed {
/// Basic info to find the cluster-wide bestMove
struct MoveInfo {
@@ -57,8 +58,8 @@ struct MoveInfo {
#ifdef USE_MPI
// store the TTEntry with its full key, so it can be saved on the receiver side
using KeyedTTEntry = std::pair<Key, TTEntry>;
// store the TTData with its (full) key, so it can be saved on the receiver side
using KeyedTTEntry = std::pair<Key, TTData>;
constexpr std::size_t TTCacheSize = 16;
// Threads locally cache their high-depth TT entries till a batch can be send by MPI
@@ -67,7 +68,7 @@ class TTCache: public std::array<KeyedTTEntry, N> {
struct Compare {
inline bool operator()(const KeyedTTEntry& lhs, const KeyedTTEntry& rhs) {
return lhs.second.depth() > rhs.second.depth();
return lhs.second.depth > rhs.second.depth;
}
};
Compare compare;
@@ -96,7 +97,7 @@ inline bool is_root() { return rank() == 0; }
void save(TranspositionTable&,
ThreadPool&,
Search::Worker* thread,
TTEntry* tte,
TTWriter ttWriter,
Key k,
Value v,
bool PvHit,
@@ -128,7 +129,7 @@ constexpr bool is_root() { return true; }
inline void save(TranspositionTable&,
ThreadPool&,
Search::Worker*,
TTEntry* tte,
TTWriter ttWriter,
Key k,
Value v,
bool PvHit,
@@ -137,7 +138,7 @@ inline void save(TranspositionTable&,
Move m,
Value ev,
uint8_t generation8) {
tte->save(k, v, PvHit, b, d, m, ev, generation8);
ttWriter.write(k, v, PvHit, b, d, m, ev, generation8);
}
inline void pick_moves(MoveInfo&, std::vector<std::vector<char>>&) {}
inline void ttSendRecvBuff_resize(size_t) {}
+1 -1
View File
@@ -164,7 +164,7 @@ void Engine::set_tt_size(size_t mb) {
tt.resize(mb, threads);
// Adjust cluster buffers
Cluster::ttSendRecvBuff_resize(threads.num_threads());
Distributed::ttSendRecvBuff_resize(threads.num_threads());
}
void Engine::set_ponderhit(bool b) { threads.main_manager()->ponder = b; }
+3 -3
View File
@@ -29,8 +29,8 @@ using namespace Stockfish;
int main(int argc, char* argv[]) {
Cluster::init();
if (Cluster::is_root())
Distributed::init();
if (Distributed::is_root())
std::cout << engine_info() << std::endl;
Bitboards::init();
@@ -42,7 +42,7 @@ int main(int argc, char* argv[]) {
uci.loop();
Cluster::finalize();
Distributed::finalize();
return 0;
}
+1 -1
View File
@@ -260,7 +260,7 @@ void Network<Arch, Transformer>::verify(std::string evalfilePath) const {
}
size_t size = sizeof(*featureTransformer) + sizeof(Arch) * LayerStacks;
if (Cluster::is_root())
if (Distributed::is_root())
sync_cout << "info string NNUE evaluation using " << evalfilePath << " ("
<< size / (1024 * 1024) << "MiB, (" << featureTransformer->InputDimensions << ", "
<< network[0].TransformedFeatureDimensions << ", " << network[0].FC_0_OUTPUTS
+1 -1
View File
@@ -51,7 +51,7 @@ uint64_t perft(Position& pos, Depth depth) {
nodes += cnt;
pos.undo_move(m);
}
if (Root && Cluster::is_root())
if (Root && Distributed::is_root())
sync_cout << UCIEngine::move(m, pos.is_chess960()) << ": " << cnt << sync_endl;
}
return nodes;
+127 -123
View File
@@ -168,7 +168,7 @@ void Search::Worker::start_searching() {
if (rootMoves.empty())
{
rootMoves.emplace_back(Move::none());
if (Cluster::is_root())
if (Distributed::is_root())
main_manager()->updates.onUpdateNoMoves(
{0, {rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW, rootPos}});
}
@@ -185,7 +185,7 @@ void Search::Worker::start_searching() {
// until the GUI sends one of those commands.
while (!threads.stop && (main_manager()->ponder || limits.infinite))
{
Cluster::signals_poll(threads);
Distributed::signals_poll(threads);
} // Busy wait for a stop or a ponder reset
// Stop the threads if not already stopped (also raise the stop if
@@ -193,7 +193,7 @@ void Search::Worker::start_searching() {
threads.stop = true;
// Signal and synchronize all other ranks
Cluster::signals_sync(threads);
Distributed::signals_sync(threads);
// Wait until all threads have finished
threads.wait_for_search_finished();
@@ -201,7 +201,7 @@ void Search::Worker::start_searching() {
// When playing in 'nodes as time' mode, subtract the searched nodes from
// the available ones before exiting.
if (limits.npmsec)
main_manager()->tm.advance_nodes_time(Cluster::nodes_searched(threads)
main_manager()->tm.advance_nodes_time(Distributed::nodes_searched(threads)
- limits.inc[rootPos.side_to_move()]);
Worker* bestThread = this;
@@ -234,13 +234,13 @@ void Search::Worker::start_searching() {
main_manager()->updates.onUpdateFull = std::move(oldOnUpdateFull);
// Exchange info as needed
Cluster::MoveInfo mi{bestMove.raw(), ponderMove.raw(), bestThread->completedDepth,
bestThread->rootMoves[0].score, Cluster::rank()};
Cluster::pick_moves(mi, serializedInfo);
Distributed::MoveInfo mi{bestMove.raw(), ponderMove.raw(), bestThread->completedDepth,
bestThread->rootMoves[0].score, Distributed::rank()};
Distributed::pick_moves(mi, serializedInfo);
main_manager()->bestPreviousScore = static_cast<Value>(mi.score);
if (Cluster::is_root())
if (Distributed::is_root())
{
// Send again PV info if we have a new best thread/rank
if (bestThread != this || mi.rank != 0)
@@ -323,7 +323,7 @@ void Search::Worker::iterative_deepening() {
// Iterative deepening loop until requested to stop or the target depth is reached
while (++rootDepth < MAX_PLY && !threads.stop
&& !(limits.depth && mainThread && Cluster::is_root() && rootDepth > limits.depth))
&& !(limits.depth && mainThread && Distributed::is_root() && rootDepth > limits.depth))
{
// Age out PV variability metric
if (mainThread)
@@ -393,11 +393,11 @@ void Search::Worker::iterative_deepening() {
// When failing high/low give some update (without cluttering
// the UI) before a re-search.
if (Cluster::is_root() && mainThread && multiPV == 1
if (Distributed::is_root() && mainThread && multiPV == 1
&& (bestValue <= alpha || bestValue >= beta) && elapsed_time() > 3000)
{
main_manager()->pv(*this, threads, tt, rootDepth);
Cluster::cluster_info(threads, rootDepth, elapsed());
Distributed::cluster_info(threads, rootDepth, elapsed());
}
// In case of failing low/high increase aspiration window and
@@ -427,7 +427,7 @@ void Search::Worker::iterative_deepening() {
// Sort the PV lines searched so far and update the GUI
std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1);
if (Cluster::is_root() && mainThread
if (Distributed::is_root() && mainThread
&& (threads.stop || pvIdx + 1 == multiPV || elapsed_time() > 3000)
// A thread that aborted search can have mated-in/TB-loss PV and score
// that cannot be trusted, i.e. it can be delayed or refuted if we would have
@@ -436,7 +436,7 @@ void Search::Worker::iterative_deepening() {
&& !(threads.abortedSearch && rootMoves[0].uciScore <= VALUE_TB_LOSS_IN_MAX_PLY))
{
main_manager()->pv(*this, threads, tt, rootDepth);
Cluster::cluster_info(threads, rootDepth, elapsed() + 1);
Distributed::cluster_info(threads, rootDepth, elapsed() + 1);
}
}
@@ -594,16 +594,15 @@ Value Search::Worker::search(
StateInfo st;
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
TTEntry* tte;
Key posKey;
Move ttMove, move, excludedMove, bestMove;
Depth extension, newDepth;
Value bestValue, value, ttValue, eval, maxValue, probCutBeta, singularValue;
bool givesCheck, improving, priorCapture, opponentWorsening;
bool capture, moveCountPruning, ttCapture;
Piece movedPiece;
int moveCount, captureCount, quietCount;
Bound singularBound;
Key posKey;
Move move, excludedMove, bestMove;
Depth extension, newDepth;
Value bestValue, value, eval, maxValue, probCutBeta, singularValue;
bool givesCheck, improving, priorCapture, opponentWorsening;
bool capture, moveCountPruning, ttCapture;
Piece movedPiece;
int moveCount, captureCount, quietCount;
Bound singularBound;
// Step 1. Initialize node
Worker* thisThread = this;
@@ -653,31 +652,32 @@ Value Search::Worker::search(
ss->statScore = 0;
// Step 4. Transposition table lookup.
excludedMove = ss->excludedMove;
posKey = pos.key();
tte = tt.probe(posKey, ss->ttHit);
ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0]
: ss->ttHit ? tte->move()
: Move::none();
ttCapture = ttMove && pos.capture_stage(ttMove);
excludedMove = ss->excludedMove;
posKey = pos.key();
auto [ttHit, ttData, ttWriter] = tt.probe(posKey);
// Need further processing of the saved data
ss->ttHit = ttHit;
ttData.move = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0]
: ttHit ? ttData.move
: Move::none();
ttData.value = ttHit ? value_from_tt(ttData.value, ss->ply, pos.rule50_count()) : VALUE_NONE;
ss->ttPv = excludedMove ? ss->ttPv : PvNode || (ttHit && ttData.is_pv);
ttCapture = ttData.move && pos.capture_stage(ttData.move);
// At this point, if excluded, skip straight to step 6, static eval. However,
// to save indentation, we list the condition in all code between here and there.
if (!excludedMove)
ss->ttPv = PvNode || (ss->ttHit && tte->is_pv());
// At non-PV nodes we check for an early TT cutoff
if (!PvNode && !excludedMove && tte->depth() > depth - (ttValue <= beta)
&& ttValue != VALUE_NONE // Possible in case of TT access race or if !ttHit
&& (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER)))
if (!PvNode && !excludedMove && ttData.depth > depth - (ttData.value <= beta)
&& ttData.value != VALUE_NONE // Can happen when !ttHit or when access race in probe()
&& (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER)))
{
// If ttMove is quiet, update move sorting heuristics on TT hit (~2 Elo)
if (ttMove && ttValue >= beta)
if (ttData.move && ttData.value >= beta)
{
// Bonus for a quiet ttMove that fails high (~2 Elo)
if (!ttCapture)
update_quiet_stats(pos, ss, *this, ttMove, stat_bonus(depth));
update_quiet_stats(pos, ss, *this, ttData.move, stat_bonus(depth));
// Extra penalty for early quiet moves of
// the previous ply (~1 Elo on STC, ~2 Elo on LTC)
@@ -689,7 +689,7 @@ Value Search::Worker::search(
// Partial workaround for the graph history interaction problem
// For high rule50 counts don't produce transposition table cutoffs.
if (pos.rule50_count() < 90)
return ttValue;
return ttData.value;
}
// Step 5. Tablebases probe
@@ -727,9 +727,10 @@ Value Search::Worker::search(
if (b == BOUND_EXACT || (b == BOUND_LOWER ? value >= beta : value <= alpha))
{
Cluster::save(tt, threads, thisThread, tte, posKey, value_to_tt(value, ss->ply),
ss->ttPv, b, std::min(MAX_PLY - 1, depth + 6), Move::none(),
VALUE_NONE, tt.generation());
Distributed::save(tt, threads, thisThread, ttWriter, posKey,
value_to_tt(value, ss->ply), ss->ttPv, b,
std::min(MAX_PLY - 1, depth + 6), Move::none(), VALUE_NONE,
tt.generation());
return value;
}
@@ -764,7 +765,7 @@ Value Search::Worker::search(
else if (ss->ttHit)
{
// Never assume anything about values stored in TT
unadjustedStaticEval = tte->eval();
unadjustedStaticEval = ttData.eval;
if (unadjustedStaticEval == VALUE_NONE)
unadjustedStaticEval =
evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]);
@@ -774,8 +775,9 @@ Value Search::Worker::search(
ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos);
// ttValue can be used as a better position evaluation (~7 Elo)
if (ttValue != VALUE_NONE && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER)))
eval = ttValue;
if (ttData.value != VALUE_NONE
&& (ttData.bound & (ttData.value > eval ? BOUND_LOWER : BOUND_UPPER)))
eval = ttData.value;
}
else
{
@@ -784,8 +786,9 @@ Value Search::Worker::search(
ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos);
// Static evaluation is saved as it was before adjustment by correction history
Cluster::save(tt, threads, thisThread, tte, posKey, VALUE_NONE, ss->ttPv, BOUND_NONE,
DEPTH_UNSEARCHED, Move::none(), unadjustedStaticEval, tt.generation());
Distributed::save(tt, threads, thisThread, ttWriter, posKey, VALUE_NONE, ss->ttPv,
BOUND_NONE, DEPTH_UNSEARCHED, Move::none(), unadjustedStaticEval,
tt.generation());
}
// Use static evaluation difference to improve quiet move ordering (~9 Elo)
@@ -826,7 +829,7 @@ Value Search::Worker::search(
&& eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening)
- (ss - 1)->statScore / 263
>= beta
&& eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture))
&& eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttData.move || ttCapture))
return beta > VALUE_TB_LOSS_IN_MAX_PLY ? beta + (eval - beta) / 3 : eval;
// Step 9. Null move search with verification search (~35 Elo)
@@ -872,7 +875,7 @@ Value Search::Worker::search(
// Step 10. Internal iterative reductions (~9 Elo)
// For PV nodes without a ttMove, we decrease depth by 3.
if (PvNode && !ttMove)
if (PvNode && !ttData.move)
depth -= 3;
// Use qsearch if depth <= 0.
@@ -881,8 +884,8 @@ Value Search::Worker::search(
// For cutNodes, if depth is high enough, decrease depth by 2 if there is no ttMove, or
// by 1 if there is a ttMove with an upper bound.
if (cutNode && depth >= 8 && (!ttMove || tte->bound() == BOUND_UPPER))
depth -= 1 + !ttMove;
if (cutNode && depth >= 8 && (!ttData.move || ttData.bound == BOUND_UPPER))
depth -= 1 + !ttData.move;
// Step 11. ProbCut (~10 Elo)
// If we have a good enough capture (or queen promotion) and a reduced search returns a value
@@ -895,11 +898,11 @@ Value Search::Worker::search(
// there and in further interactions with transposition table cutoff depth is set to depth - 3
// because probCut search has depth set to depth - 4 but we also do a move before it
// So effective depth is equal to depth - 3
&& !(tte->depth() >= depth - 3 && ttValue != VALUE_NONE && ttValue < probCutBeta))
&& !(ttData.depth >= depth - 3 && ttData.value != VALUE_NONE && ttData.value < probCutBeta))
{
assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta);
MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &thisThread->captureHistory);
MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &thisThread->captureHistory);
while ((move = mp.next_move()) != Move::none())
if (move != excludedMove && pos.legal(move))
@@ -930,9 +933,9 @@ Value Search::Worker::search(
if (value >= probCutBeta)
{
// Save ProbCut data into transposition table
Cluster::save(tt, threads, thisThread, tte, posKey, value_to_tt(value, ss->ply),
ss->ttPv, BOUND_LOWER, depth - 3, move, unadjustedStaticEval,
tt.generation());
Distributed::save(tt, threads, thisThread, ttWriter, posKey,
value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3,
move, unadjustedStaticEval, tt.generation());
return std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY ? value - (probCutBeta - beta)
: value;
}
@@ -945,9 +948,10 @@ moves_loop: // When in check, search starts here
// Step 12. A small Probcut idea, when we are in check (~4 Elo)
probCutBeta = beta + 388;
if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER)
&& tte->depth() >= depth - 4 && ttValue >= probCutBeta
&& std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY)
if (ss->inCheck && !PvNode && ttCapture && (ttData.bound & BOUND_LOWER)
&& ttData.depth >= depth - 4 && ttData.value >= probCutBeta
&& std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY
&& std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY)
return probCutBeta;
const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory,
@@ -960,7 +964,7 @@ moves_loop: // When in check, search starts here
Move countermove =
prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : Move::none();
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory,
MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->captureHistory,
contHist, &thisThread->pawnHistory, countermove, ss->killers);
value = bestValue;
@@ -991,7 +995,7 @@ moves_loop: // When in check, search starts here
ss->moveCount = ++moveCount;
if (rootNode && Cluster::is_root() && is_mainthread() && elapsed_time() > 3000)
if (rootNode && Distributed::is_root() && is_mainthread() && elapsed_time() > 3000)
{
main_manager()->updates.onIter(
{depth, UCIEngine::move(move, pos.is_chess960()), moveCount + thisThread->pvIdx});
@@ -1095,12 +1099,12 @@ moves_loop: // When in check, search starts here
// Generally, higher singularBeta (i.e closer to ttValue) and lower extension
// margins scale well.
if (!rootNode && move == ttMove && !excludedMove
if (!rootNode && move == ttData.move && !excludedMove
&& depth >= 4 - (thisThread->completedDepth > 35) + ss->ttPv
&& std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER)
&& tte->depth() >= depth - 3)
&& std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY && (ttData.bound & BOUND_LOWER)
&& ttData.depth >= depth - 3)
{
Value singularBeta = ttValue - (52 + 80 * (ss->ttPv && !PvNode)) * depth / 64;
Value singularBeta = ttData.value - (52 + 80 * (ss->ttPv && !PvNode)) * depth / 64;
Depth singularDepth = newDepth / 2;
ss->excludedMove = move;
@@ -1135,7 +1139,7 @@ moves_loop: // When in check, search starts here
// so we reduce the ttMove in favor of other moves based on some conditions:
// If the ttMove is assumed to fail high over current beta (~7 Elo)
else if (ttValue >= beta)
else if (ttData.value >= beta)
extension = -3;
// If we are on a cutNode but the ttMove is not assumed to fail high over current beta (~1 Elo)
@@ -1175,7 +1179,7 @@ moves_loop: // When in check, search starts here
// Decrease reduction if position is or has been on the PV (~7 Elo)
if (ss->ttPv)
r -= 1 + (ttValue > alpha) + (tte->depth() >= depth);
r -= 1 + (ttData.value > alpha) + (ttData.depth >= depth);
// Decrease reduction for PvNodes (~0 Elo on STC, ~2 Elo on LTC)
if (PvNode)
@@ -1185,8 +1189,8 @@ moves_loop: // When in check, search starts here
// Increase reduction for cut nodes (~4 Elo)
if (cutNode)
r += 2 - (tte->depth() >= depth && ss->ttPv)
+ (!ss->ttPv && move != ttMove && move != ss->killers[0]);
r += 2 - (ttData.depth >= depth && ss->ttPv)
+ (!ss->ttPv && move != ttData.move && move != ss->killers[0]);
// Increase reduction if ttMove is a capture (~3 Elo)
if (ttCapture)
@@ -1198,7 +1202,7 @@ moves_loop: // When in check, search starts here
// For first picked move (ttMove) reduce reduction
// but never allow it to go below 0 (~3 Elo)
else if (move == ttMove)
else if (move == ttData.move)
r = std::max(0, r - 2);
ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()]
@@ -1246,7 +1250,7 @@ moves_loop: // When in check, search starts here
else if (!PvNode || moveCount > 1)
{
// Increase reduction if ttMove is not present (~6 Elo)
if (!ttMove)
if (!ttData.move)
r += 2;
// Note that if expected reduction is high, we reduce search depth by 1 here (~9 Elo)
@@ -1336,7 +1340,7 @@ moves_loop: // When in check, search starts here
if (value >= beta)
{
ss->cutoffCnt += 1 + !ttMove - (extension >= 2);
ss->cutoffCnt += 1 + !ttData.move - (extension >= 2);
assert(value >= beta); // Fail high
break;
}
@@ -1412,12 +1416,12 @@ moves_loop: // When in check, search starts here
// Write gathered information in transposition table
// Static evaluation is saved as it was before correction history
if (!excludedMove && !(rootNode && thisThread->pvIdx))
Cluster::save(tt, threads, thisThread, tte, posKey, value_to_tt(bestValue, ss->ply),
ss->ttPv,
bestValue >= beta ? BOUND_LOWER
: PvNode && bestMove ? BOUND_EXACT
: BOUND_UPPER,
depth, bestMove, unadjustedStaticEval, tt.generation());
Distributed::save(tt, threads, thisThread, ttWriter, posKey,
value_to_tt(bestValue, ss->ply), ss->ttPv,
bestValue >= beta ? BOUND_LOWER
: PvNode && bestMove ? BOUND_EXACT
: BOUND_UPPER,
depth, bestMove, unadjustedStaticEval, tt.generation());
// Adjust correction history
if (!ss->inCheck && (!bestMove || !pos.capture(bestMove))
@@ -1464,14 +1468,12 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
StateInfo st;
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
TTEntry* tte;
Key posKey;
Move ttMove, move, bestMove;
Depth ttDepth;
Value bestValue, value, ttValue, futilityBase;
bool pvHit, givesCheck, capture;
int moveCount;
Color us = pos.side_to_move();
Key posKey;
Move move, bestMove;
Value bestValue, value, futilityBase;
bool pvHit, givesCheck, capture;
int moveCount;
Color us = pos.side_to_move();
// Step 1. Initialize node
if (PvNode)
@@ -1497,23 +1499,25 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
assert(0 <= ss->ply && ss->ply < MAX_PLY);
// Note that unlike regular search, which stores literal depth, in QS we only store the
// current movegen stage. If in check, we search all evasions and thus store
// DEPTH_QS_CHECKS. (Evasions may be quiet, and _CHECKS includes quiets.)
ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NORMAL;
// Note that unlike regular search, which stores the literal depth into the TT, from QS we
// only store the current movegen stage as "depth". If in check, we search all evasions and
// thus store DEPTH_QS_CHECKS. (Evasions may be quiet, and _CHECKS includes quiets.)
Depth qsTtDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NORMAL;
// Step 3. Transposition table lookup
posKey = pos.key();
tte = tt.probe(posKey, ss->ttHit);
ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
ttMove = ss->ttHit ? tte->move() : Move::none();
pvHit = ss->ttHit && tte->is_pv();
posKey = pos.key();
auto [ttHit, ttData, ttWriter] = tt.probe(posKey);
// Need further processing of the saved data
ss->ttHit = ttHit;
ttData.move = ttHit ? ttData.move : Move::none();
ttData.value = ttHit ? value_from_tt(ttData.value, ss->ply, pos.rule50_count()) : VALUE_NONE;
pvHit = ttHit && ttData.is_pv;
// At non-PV nodes we check for an early TT cutoff
if (!PvNode && tte->depth() >= ttDepth
&& ttValue != VALUE_NONE // Only in case of TT access race or if !ttHit
&& (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER)))
return ttValue;
if (!PvNode && ttData.depth >= qsTtDepth
&& ttData.value != VALUE_NONE // Can happen when !ttHit or when access race in probe()
&& (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER)))
return ttData.value;
// Step 4. Static evaluation of the position
Value unadjustedStaticEval = VALUE_NONE;
@@ -1524,7 +1528,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
if (ss->ttHit)
{
// Never assume anything about values stored in TT
unadjustedStaticEval = tte->eval();
unadjustedStaticEval = ttData.eval;
if (unadjustedStaticEval == VALUE_NONE)
unadjustedStaticEval =
evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]);
@@ -1532,9 +1536,9 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos);
// ttValue can be used as a better position evaluation (~13 Elo)
if (std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY
&& (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER)))
bestValue = ttValue;
if (std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY
&& (ttData.bound & (ttData.value > bestValue ? BOUND_LOWER : BOUND_UPPER)))
bestValue = ttData.value;
}
else
{
@@ -1553,9 +1557,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && !PvNode)
bestValue = (3 * bestValue + beta) / 4;
if (!ss->ttHit)
Cluster::save(tt, threads, thisThread, tte, posKey, value_to_tt(bestValue, ss->ply),
false, BOUND_LOWER, DEPTH_UNSEARCHED, Move::none(),
unadjustedStaticEval, tt.generation());
Distributed::save(tt, threads, thisThread, ttWriter, posKey,
value_to_tt(bestValue, ss->ply), false, BOUND_LOWER,
DEPTH_UNSEARCHED, Move::none(), unadjustedStaticEval,
tt.generation());
return bestValue;
}
@@ -1575,7 +1580,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
// (Presently, having the checks stage is worth only 1 Elo, and may be removable in the near future,
// which would result in only a single stage of QS movegen.)
Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE;
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory,
MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->captureHistory,
contHist, &thisThread->pawnHistory);
// Step 5. Loop through all pseudo-legal moves until no moves remain or a beta cutoff occurs.
@@ -1694,9 +1699,9 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
// Save gathered info in transposition table
// Static evaluation is saved as it was before adjustment by correction history
Cluster::save(tt, threads, thisThread, tte, posKey, value_to_tt(bestValue, ss->ply), pvHit,
bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, ttDepth, bestMove,
unadjustedStaticEval, tt.generation());
Distributed::save(tt, threads, thisThread, ttWriter, posKey, value_to_tt(bestValue, ss->ply),
pvHit, bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, qsTtDepth, bestMove,
unadjustedStaticEval, tt.generation());
assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
@@ -1717,7 +1722,7 @@ Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) const {
// This function is intended for use only when printing PV outputs, and not used
// for making decisions within the search algorithm itself.
TimePoint Search::Worker::elapsed() const {
return main_manager()->tm.elapsed([this]() { return Cluster::nodes_searched(threads); });
return main_manager()->tm.elapsed([this]() { return Distributed::nodes_searched(threads); });
}
TimePoint Search::Worker::elapsed_time() const { return main_manager()->tm.elapsed_time(); }
@@ -1941,8 +1946,9 @@ void SearchManager::check_time(Search::Worker& worker) {
static TimePoint lastInfoTime = now();
TimePoint elapsed = tm.elapsed([&worker]() { return Cluster::nodes_searched(worker.threads); });
TimePoint tick = worker.limits.startTime + elapsed;
TimePoint elapsed =
tm.elapsed([&worker]() { return Distributed::nodes_searched(worker.threads); });
TimePoint tick = worker.limits.startTime + elapsed;
if (tick - lastInfoTime >= 1000)
{
@@ -1951,7 +1957,7 @@ void SearchManager::check_time(Search::Worker& worker) {
}
// poll on MPI signals
Cluster::signals_poll(worker.threads);
Distributed::signals_poll(worker.threads);
// We should not stop pondering until told so by the GUI
if (ponder)
@@ -1964,7 +1970,7 @@ void SearchManager::check_time(Search::Worker& worker) {
&& ((worker.limits.use_time_management() && (elapsed > tm.maximum() || stopOnPonderhit))
|| (worker.limits.movetime && elapsed >= worker.limits.movetime)
|| (worker.limits.nodes
&& Cluster::nodes_searched(worker.threads) >= worker.limits.nodes)))
&& Distributed::nodes_searched(worker.threads) >= worker.limits.nodes)))
worker.threads.stop = worker.threads.abortedSearch = true;
}
@@ -1973,13 +1979,14 @@ void SearchManager::pv(const Search::Worker& worker,
const TranspositionTable& tt,
Depth depth) const {
const auto nodes = Cluster::nodes_searched(threads);
const auto nodes = Distributed::nodes_searched(threads);
const auto& rootMoves = worker.rootMoves;
const auto& pos = worker.rootPos;
size_t pvIdx = worker.pvIdx;
TimePoint time = tm.elapsed_time() + 1;
size_t multiPV = std::min(size_t(worker.options["MultiPV"]), rootMoves.size());
uint64_t tbHits = Cluster::tb_hits(threads) + (worker.tbConfig.rootInTB ? rootMoves.size() : 0);
uint64_t tbHits =
Distributed::tb_hits(threads) + (worker.tbConfig.rootInTB ? rootMoves.size() : 0);
for (size_t i = 0; i < multiPV; ++i)
{
@@ -2041,20 +2048,17 @@ bool RootMove::extract_ponder_from_tt(const TranspositionTable& tt, Position& po
StateInfo st;
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
bool ttHit;
assert(pv.size() == 1);
if (pv[0] == Move::none())
return false;
pos.do_move(pv[0], st);
TTEntry* tte = tt.probe(pos.key(), ttHit);
auto [ttHit, ttData, ttWriter] = tt.probe(pos.key());
if (ttHit)
{
Move m = tte->move(); // Local copy to be SMP safe
if (MoveList<LEGAL>(pos).contains(m))
pv.push_back(m);
if (MoveList<LEGAL>(pos).contains(ttData.move))
pv.push_back(ttData.move);
}
pos.undo_move(pv[0]);
+17 -15
View File
@@ -119,7 +119,9 @@ struct LimitsType {
ponderMode = false;
}
bool use_time_management() const { return Cluster::is_root() && (time[WHITE] || time[BLACK]); }
bool use_time_management() const {
return Distributed::is_root() && (time[WHITE] || time[BLACK]);
}
std::vector<std::string> searchmoves;
TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;
@@ -266,25 +268,25 @@ class Worker {
#ifdef USE_MPI
struct {
std::mutex mutex;
Cluster::TTCache<Cluster::TTCacheSize> buffer = {};
std::mutex mutex;
Distributed::TTCache<Distributed::TTCacheSize> buffer = {};
} ttCache;
#endif
std::atomic<uint64_t> TTsaves;
friend void Cluster::save(TranspositionTable&,
ThreadPool&,
Search::Worker*,
TTEntry* tte,
Key k,
Value v,
bool PvHit,
Bound b,
Depth d,
Move m,
Value ev,
uint8_t generation8);
friend void Distributed::save(TranspositionTable&,
ThreadPool&,
Search::Worker*,
TTWriter ttWriter,
Key k,
Value v,
bool PvHit,
Bound b,
Depth d,
Move m,
Value ev,
uint8_t generation8);
private:
void iterative_deepening();
+1 -1
View File
@@ -1467,7 +1467,7 @@ void Tablebases::init(const std::string& paths) {
}
}
if (Cluster::is_root())
if (Distributed::is_root())
sync_cout << "info string Found " << TBTables.size() << " tablebases" << sync_endl;
}
+1 -1
View File
@@ -296,7 +296,7 @@ void ThreadPool::start_thinking(const OptionsMap& options,
for (auto&& th : threads)
th->wait_for_search_finished();
Cluster::signals_init();
Distributed::signals_init();
main_thread()->start_searching();
}
+119 -29
View File
@@ -25,11 +25,63 @@
#include <iostream>
#include "memory.h"
#include "misc.h"
#include "syzygy/tbprobe.h"
#include "thread.h"
namespace Stockfish {
// TTEntry struct is the 10 bytes transposition table entry, defined as below:
//
// key 16 bit
// depth 8 bit
// generation 5 bit
// pv node 1 bit
// bound type 2 bit
// move 16 bit
// value 16 bit
// evaluation 16 bit
//
// These fields are in the same order as accessed by TT::probe(), since memory is fastest sequentially.
// Equally, the store order in save() matches this order.
struct TTEntry {
// Convert internal bitfields to external types
TTData read() const {
return TTData{Move(move16), Value(value16),
Value(eval16), Depth(depth8 + DEPTH_ENTRY_OFFSET),
Bound(genBound8 & 0x3), bool(genBound8 & 0x4)};
}
void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8);
// The returned age is a multiple of TranspositionTable::GENERATION_DELTA
uint8_t relative_age(const uint8_t generation8) const;
private:
friend class TranspositionTable;
uint16_t key16;
uint8_t depth8;
uint8_t genBound8;
Move move16;
int16_t value16;
int16_t eval16;
};
// `genBound8` is where most of the details are. We use the following constants to manipulate 5 leading generation bits
// and 3 trailing miscellaneous bits.
// These bits are reserved for other things.
static constexpr unsigned GENERATION_BITS = 3;
// increment for generation field
static constexpr int GENERATION_DELTA = (1 << GENERATION_BITS);
// cycle length
static constexpr int GENERATION_CYCLE = 255 + GENERATION_DELTA;
// mask to pull out generation number
static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF;
// DEPTH_ENTRY_OFFSET exists because 1) we use `bool(depth8)` as the occupancy check, but
// 2) we need to store negative depths for QS. (`depth8` is the only field with "spare bits":
// we sacrifice the ability to store depths greater than 1<<8 less the offset, as asserted below.)
@@ -65,12 +117,34 @@ uint8_t TTEntry::relative_age(const uint8_t generation8) const {
// is needed to keep the unrelated lowest n bits from affecting
// the result) to calculate the entry age correctly even after
// generation8 overflows into the next cycle.
return (TranspositionTable::GENERATION_CYCLE + generation8 - genBound8)
& TranspositionTable::GENERATION_MASK;
return (GENERATION_CYCLE + generation8 - genBound8) & GENERATION_MASK;
}
// TTWriter is but a very thin wrapper around the pointer
TTWriter::TTWriter(TTEntry* tte) :
entry(tte) {}
void TTWriter::write(
Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8) {
entry->save(k, v, pv, b, d, m, ev, generation8);
}
// A TranspositionTable is an array of Cluster, of size clusterCount. Each cluster consists of ClusterSize number
// of TTEntry. Each non-empty TTEntry contains information on exactly one position. The size of a Cluster should
// divide the size of a cache line for best performance, as the cacheline is prefetched when possible.
static constexpr int ClusterSize = 3;
struct Cluster {
TTEntry entry[ClusterSize];
char padding[2]; // Pad to 32 bytes
};
static_assert(sizeof(Cluster) == 32, "Suboptimal Cluster size");
// Sets the size of the transposition table,
// measured in megabytes. Transposition table consists
// of clusters and each cluster consists of ClusterSize number of TTEntry.
@@ -114,32 +188,6 @@ void TranspositionTable::clear(ThreadPool& threads) {
}
// Looks up the current position in the transposition
// table. It returns true and a pointer to the TTEntry if the position is found.
// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry
// to be replaced later. The replace value of an entry is calculated as its depth
// minus 8 times its relative age. TTEntry t1 is considered more valuable than
// TTEntry t2 if its replace value is greater than that of t2.
TTEntry* TranspositionTable::probe(const Key key, bool& found) const {
TTEntry* const tte = first_entry(key);
const uint16_t key16 = uint16_t(key); // Use the low 16 bits as key inside the cluster
for (int i = 0; i < ClusterSize; ++i)
if (tte[i].key16 == key16)
return found = bool(tte[i].depth8), &tte[i];
// Find an entry to be replaced according to the replacement strategy
TTEntry* replace = tte;
for (int i = 1; i < ClusterSize; ++i)
if (replace->depth8 - replace->relative_age(generation8) * 2
> tte[i].depth8 - tte[i].relative_age(generation8) * 2)
replace = &tte[i];
return found = false, replace;
}
// Returns an approximation of the hashtable
// occupation during a search. The hash is x permill full, as per UCI protocol.
// Only counts entries which match the current generation.
@@ -154,4 +202,46 @@ int TranspositionTable::hashfull() const {
return cnt / ClusterSize;
}
void TranspositionTable::new_search() {
// increment by delta to keep lower bits as is
generation8 += GENERATION_DELTA;
}
uint8_t TranspositionTable::generation() const { return generation8; }
// Looks up the current position in the transposition
// table. It returns true if the position is found.
// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry
// to be replaced later. The replace value of an entry is calculated as its depth
// minus 8 times its relative age. TTEntry t1 is considered more valuable than
// TTEntry t2 if its replace value is greater than that of t2.
std::tuple<bool, TTData, TTWriter> TranspositionTable::probe(const Key key) const {
TTEntry* const tte = first_entry(key);
const uint16_t key16 = uint16_t(key); // Use the low 16 bits as key inside the cluster
for (int i = 0; i < ClusterSize; ++i)
if (tte[i].key16 == key16)
// This gap is the main place for read races.
// After `read()` completes that copy is final, but may be self-inconsistent.
return {bool(tte[i].depth8), tte[i].read(), TTWriter(&tte[i])};
// Find an entry to be replaced according to the replacement strategy
TTEntry* replace = tte;
for (int i = 1; i < ClusterSize; ++i)
if (replace->depth8 - replace->relative_age(generation8) * 2
> tte[i].depth8 - tte[i].relative_age(generation8) * 2)
replace = &tte[i];
return {false, replace->read(), TTWriter(replace)};
}
TTEntry* TranspositionTable::first_entry(const Key key) const {
return &table[mul_hi64(key, clusterCount)].entry[0];
}
} // namespace Stockfish
+49 -76
View File
@@ -21,111 +21,84 @@
#include <cstddef>
#include <cstdint>
#include <tuple>
#include "memory.h"
#include "misc.h"
#include "types.h"
namespace Stockfish {
namespace Cluster {
namespace Distributed {
void init();
}
/// TTEntry struct is the 10 bytes transposition table entry, defined as below:
///
/// key 16 bit
/// depth 8 bit
/// generation 5 bit
/// pv node 1 bit
/// bound type 2 bit
/// move 16 bit
/// value 16 bit
/// eval value 16 bit
//
// These fields are in the same order as accessed by TT::probe(), since memory is fastest sequentially.
// Equally, the store order in save() matches this order.
struct TTEntry {
class ThreadPool;
struct TTEntry;
struct Cluster;
Move move() const { return Move(move16); }
Value value() const { return Value(value16); }
Value eval() const { return Value(eval16); }
Depth depth() const { return Depth(depth8 + DEPTH_ENTRY_OFFSET); }
bool is_pv() const { return bool(genBound8 & 0x4); }
Bound bound() const { return Bound(genBound8 & 0x3); }
void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8);
// The returned age is a multiple of TranspositionTable::GENERATION_DELTA
uint8_t relative_age(const uint8_t generation8) const;
// There is only one global hash table for the engine and all its threads. For chess in particular, we even allow racy
// updates between threads to and from the TT, as taking the time to synchronize access would cost thinking time and
// thus elo. As a hash table, collisions are possible and may cause chess playing issues (bizarre blunders, faulty mate
// reports, etc). Fixing these also loses elo; however such risk decreases quickly with larger TT size.
//
// `probe` is the primary method: given a board position, we lookup its entry in the table, and return a tuple of:
// 1) whether the entry already has this position
// 2) a copy of the prior data (if any) (may be inconsistent due to read races)
// 3) a writer object to this entry
// The copied data and the writer are separated to maintain clear boundaries between local vs global objects.
// A copy of the data already in the entry (possibly collided). `probe` may be racy, resulting in inconsistent data.
struct TTData {
Move move;
Value value, eval;
Depth depth;
Bound bound;
bool is_pv;
};
// This is used to make racy writes to the global TT.
struct TTWriter {
public:
void write(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8);
private:
friend class TranspositionTable;
friend void Cluster::init();
friend void Distributed::init();
uint16_t key16;
uint8_t depth8;
uint8_t genBound8;
Move move16;
int16_t value16;
int16_t eval16;
TTEntry* entry;
TTWriter(TTEntry* tte);
};
class ThreadPool;
// A TranspositionTable is an array of Cluster, of size clusterCount. Each
// cluster consists of ClusterSize number of TTEntry. Each non-empty TTEntry
// contains information on exactly one position. The size of a Cluster should
// divide the size of a cache line for best performance, as the cacheline is
// prefetched when possible.
class TranspositionTable {
friend void Cluster::init();
static constexpr int ClusterSize = 3;
struct Cluster {
TTEntry entry[ClusterSize];
char padding[2]; // Pad to 32 bytes
};
static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size");
// Constants used to refresh the hash table periodically
// We have 8 bits available where the lowest 3 bits are
// reserved for other things.
static constexpr unsigned GENERATION_BITS = 3;
// increment for generation field
static constexpr int GENERATION_DELTA = (1 << GENERATION_BITS);
// cycle length
static constexpr int GENERATION_CYCLE = 255 + GENERATION_DELTA;
// mask to pull out generation number
static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF;
friend void Distributed::init();
public:
~TranspositionTable() { aligned_large_pages_free(table); }
void new_search() {
// increment by delta to keep lower bits as is
generation8 += GENERATION_DELTA;
}
TTEntry* probe(const Key key, bool& found) const;
int hashfull() const;
void resize(size_t mbSize, ThreadPool& threads);
void clear(ThreadPool& threads);
void resize(size_t mbSize, ThreadPool& threads); // Set TT size
void clear(ThreadPool& threads); // Re-initialize memory, multithreaded
int hashfull()
const; // Approximate what fraction of entries (permille) have been written to during this root search
TTEntry* first_entry(const Key key) const {
return &table[mul_hi64(key, clusterCount)].entry[0];
}
uint8_t generation() const { return generation8; }
void
new_search(); // This must be called at the beginning of each root search to track entry aging
uint8_t generation() const; // The current age, used when writing new data to the TT
std::tuple<bool, TTData, TTWriter>
probe(const Key key) const; // The main method, whose retvals separate local vs global objects
TTEntry* first_entry(const Key key)
const; // This is the hash function; its only external use is memory prefetching.
private:
friend struct TTEntry;
size_t clusterCount;
Cluster* table = nullptr;
uint8_t generation8 = 0; // Size must be not bigger than TTEntry::genBound8
Cluster* table = nullptr;
uint8_t generation8 = 0; // Size must be not bigger than TTEntry::genBound8
};
} // namespace Stockfish
+14 -14
View File
@@ -114,8 +114,8 @@ void UCIEngine::loop() {
do
{
if (cli.argc == 1
&& !Cluster::getline(std::cin,
cmd)) // Wait for an input or an end-of-file (EOF) indication
&& !Distributed::getline(std::cin,
cmd)) // Wait for an input or an end-of-file (EOF) indication
cmd = "quit";
std::istringstream is(cmd);
@@ -133,7 +133,7 @@ void UCIEngine::loop() {
else if (token == "ponderhit")
engine.set_ponderhit(false);
else if (token == "uci" && Cluster::is_root())
else if (token == "uci" && Distributed::is_root())
{
sync_cout << "id name " << engine_info(true) << "\n"
<< engine.get_options() << sync_endl;
@@ -152,7 +152,7 @@ void UCIEngine::loop() {
position(is);
else if (token == "ucinewgame")
engine.search_clear();
else if (token == "isready" && Cluster::is_root())
else if (token == "isready" && Distributed::is_root())
sync_cout << "readyok" << sync_endl;
// Add custom non-UCI commands, mainly for debugging purposes.
@@ -161,13 +161,13 @@ void UCIEngine::loop() {
engine.flip();
else if (token == "bench")
bench(is);
else if (token == "d" && Cluster::is_root())
else if (token == "d" && Distributed::is_root())
sync_cout << engine.visualize() << sync_endl;
else if (token == "eval" && Cluster::is_root())
else if (token == "eval" && Distributed::is_root())
engine.trace_eval();
else if (token == "compiler" && Cluster::is_root())
else if (token == "compiler" && Distributed::is_root())
sync_cout << compiler_info() << sync_endl;
else if (token == "export_net" && Cluster::is_root())
else if (token == "export_net" && Distributed::is_root())
{
std::pair<std::optional<std::string>, std::string> files[2];
@@ -181,7 +181,7 @@ void UCIEngine::loop() {
}
else if ((token == "--help" || token == "help" || token == "--license"
|| token == "license")
&& Cluster::is_root())
&& Distributed::is_root())
sync_cout
<< "\nStockfish is a powerful chess engine for playing and analyzing."
"\nIt is released as free software licensed under the GNU GPLv3 License."
@@ -190,7 +190,7 @@ void UCIEngine::loop() {
"\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme"
"\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n"
<< sync_endl;
else if (!token.empty() && token[0] != '#' && Cluster::is_root())
else if (!token.empty() && token[0] != '#' && Distributed::is_root())
sync_cout << "Unknown command: '" << cmd << "'. Type help for more information."
<< sync_endl;
@@ -293,7 +293,7 @@ void UCIEngine::bench(std::istream& args) {
if (token == "go" || token == "eval")
{
if (Cluster::is_root())
if (Distributed::is_root())
std::cerr << "\nPosition: " << cnt++ << '/' << num << " (" << engine.fen() << ")"
<< std::endl;
if (token == "go")
@@ -311,7 +311,7 @@ void UCIEngine::bench(std::istream& args) {
nodes += nodesSearched;
nodesSearched = 0;
}
else if (Cluster::is_root())
else if (Distributed::is_root())
engine.trace_eval();
}
else if (token == "setoption")
@@ -329,7 +329,7 @@ void UCIEngine::bench(std::istream& args) {
dbg_print();
if (Cluster::is_root())
if (Distributed::is_root())
std::cerr << "\n===========================" //
<< "\nTotal time (ms) : " << elapsed //
<< "\nNodes searched : " << nodes //
@@ -347,7 +347,7 @@ void UCIEngine::setoption(std::istringstream& is) {
std::uint64_t UCIEngine::perft(const Search::LimitsType& limits) {
auto nodes = engine.perft(engine.fen(), limits.perft, engine.get_options()["UCI_Chess960"]);
if (Cluster::is_root())
if (Distributed::is_root())
sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl;
return nodes;
}
+1 -1
View File
@@ -52,7 +52,7 @@ void OptionsMap::setoption(std::istringstream& is) {
if (options_map.count(name))
options_map[name] = value;
else if (Cluster::is_root())
else if (Distributed::is_root())
sync_cout << "No such option: " << name << sync_endl;
}
+1 -6
View File
@@ -39,13 +39,8 @@ case $1 in
threads="2"
cat << EOF > tsan.supp
race:Stockfish::TTEntry::move
race:Stockfish::TTEntry::depth
race:Stockfish::TTEntry::bound
race:Stockfish::TTEntry::read
race:Stockfish::TTEntry::save
race:Stockfish::TTEntry::value
race:Stockfish::TTEntry::eval
race:Stockfish::TTEntry::is_pv
race:Stockfish::TranspositionTable::probe
race:Stockfish::TranspositionTable::hashfull