Line data Source code
1 : #include "com.hpp"
2 : #include "distributed.h"
3 : #include "dynamicConfig.hpp"
4 : #include "energyMonitor.hpp"
5 : #include "logging.hpp"
6 : #include "searcher.hpp"
7 : #include "skill.hpp"
8 : #include "timeMan.hpp"
9 : #include "uci.hpp"
10 : #include "xboard.hpp"
11 :
12 : namespace {
13 : // Sizes and phases of the skip-blocks, used for distributing search depths across the threads, from stockfish
14 : constexpr unsigned int threadSkipSize = 20;
15 : constexpr array1d<int,threadSkipSize> skipSize = {1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4};
16 : constexpr array1d<int,threadSkipSize> skipPhase = {0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7};
17 : } // namespace
18 :
19 : // Output following chosen protocol
20 1363 : void Searcher::displayGUI(DepthType depth,
21 : DepthType seldepth,
22 : ScoreType bestScore,
23 : unsigned int ply,
24 : const PVList& pv,
25 : int multipv,
26 : const std::string& mark) {
27 1363 : const auto now = Clock::now();
28 : const TimeType ms = getTimeDiff(startTime);
29 1363 : getSearchData().times[depth] = ms;
30 1363 : if (subSearch) return; // no need to display stuff for subsearch
31 274 : std::stringstream str;
32 274 : const Counter nodeCount = ThreadPool::instance().counter(Stats::sid_nodes) + ThreadPool::instance().counter(Stats::sid_qnodes);
33 274 : if (Logging::ct == Logging::CT_xboard) {
34 0 : str << static_cast<int>(depth) << " "
35 0 : << bestScore << " "
36 0 : << ms / 10 << " " // csec
37 0 : << nodeCount << " ";
38 0 : if (DynamicConfig::fullXboardOutput)
39 0 : str << static_cast<int>(seldepth) << " "
40 0 : << static_cast<Counter>(nodeCount / ms) << " " // knps
41 0 : << ThreadPool::instance().counter(Stats::sid_tbHit1) + ThreadPool::instance().counter(Stats::sid_tbHit2);
42 0 : str << "\t" << ToString(pv);
43 0 : if (!mark.empty()) str << mark;
44 : }
45 274 : else if (Logging::ct == Logging::CT_uci) {
46 306 : const std::string multiPVstr = DynamicConfig::multiPV > 1 ? (" multipv " + std::to_string(multipv)) : "";
47 : str << "info" << multiPVstr
48 : << " depth " << static_cast<int>(depth)
49 274 : << " score " << UCI::uciScore(bestScore, ply)
50 274 : << " time " << ms // msec
51 : << " nodes " << nodeCount
52 274 : << " nps " << static_cast<Counter>(static_cast<double>(nodeCount) / msec2sec(ms)) // nps
53 274 : << " seldepth " << static_cast<int>(seldepth) << " tbhits "
54 274 : << ThreadPool::instance().counter(Stats::sid_tbHit1) + ThreadPool::instance().counter(Stats::sid_tbHit2);
55 274 : static auto lastHashFull = Clock::now(); ///@todo slow here because of guard variable
56 307 : if (getTimeDiff(now,lastHashFull) > 500 &&
57 33 : getTimeDiff(now,startTime)*2 < ThreadPool::instance().main().getCurrentMoveMs()) {
58 33 : lastHashFull = now;
59 33 : str << " hashfull " << TT::hashFull();
60 : }
61 548 : str << " pv " << ToString(pv); //only add pv at the end (c-chess-cli doesn't like to read something after pv...)
62 : }
63 : #ifdef WITH_FMTLIB
64 : else if (Logging::ct == Logging::CT_pretty){
65 : str << fmt::format(fmt::fg(fmt::color::steel_blue) | fmt::emphasis::italic, "{:>3}/{:<3} ", depth, seldepth);
66 : if(bestScore > 100){
67 : str << fmt::format(fmt::fg(fmt::color::forest_green) | fmt::emphasis::bold, "{:>6} ", UCI::uciScore(bestScore, ply));
68 : }
69 : else if (bestScore < -100){
70 : str << fmt::format(fmt::fg(fmt::color::fuchsia) | fmt::emphasis::bold, "{:>6} ", UCI::uciScore(bestScore, ply));
71 : }
72 : else str << fmt::format(fmt::fg(fmt::color::light_gray) | fmt::emphasis::bold, "{:>6} ", UCI::uciScore(bestScore, ply));
73 : str << fmt::format(fmt::fg(fmt::color::steel_blue) , "{:>6}.{:0>3}s ", ms/1000, ms-(ms/1000)*1000);
74 : str << fmt::format(fmt::fg(fmt::color::steel_blue) | fmt::emphasis::faint, "{:>6} knps ", static_cast<Counter>(static_cast<double>(nodeCount)/ 1000 / msec2sec(ms)));
75 : str << fmt::format(fmt::fg(fmt::color::steel_blue) | fmt::emphasis::faint, "{:>12} nodes ", nodeCount);
76 : str << fmt::format(fmt::fg(fmt::color::floral_white) | fmt::emphasis::faint, ToString(pv));
77 : }
78 : #endif
79 274 : Logging::LogIt(Logging::logGUI) << str.str();
80 274 : }
81 :
82 223 : void Searcher::searchDriver(bool postMove) {
83 : //stopFlag = false; // shall be only done outside to avoid race condition
84 223 : height_ = 0;
85 :
86 431 : if (isMainThread()) Distributed::sync(Distributed::_commStat2, "start of search driver");
87 :
88 : // we start by a copy, because position object must be mutable here.
89 223 : Position p(_data.p);
90 : #ifdef WITH_NNUE
91 : // Create an evaluator and reset it with the current position
92 223 : NNUEEvaluator nnueEvaluator;
93 : p.associateEvaluator(nnueEvaluator);
94 223 : p.resetNNUEEvaluator(nnueEvaluator);
95 : #endif
96 :
97 : //if (isMainThread()) p.initCaslingPermHashTable(); // let's be sure ...
98 :
99 : // requested depth can be changed according to level or skill parameter
100 223 : DynamicConfig::level = DynamicConfig::limitStrength ? Skill::convertElo2Level() : DynamicConfig::level;
101 223 : DepthType maxDepth = _data.depth; // _data.depth will be reset to zero later
102 : bool depthLimitedSearch = false;
103 : if (Distributed::isMainProcess()) {
104 243 : maxDepth = asLeastOne( (Skill::enabled() && !DynamicConfig::nodesBasedLevel) ? std::min(maxDepth, Skill::limitedDepth()) : maxDepth );
105 : depthLimitedSearch = maxDepth != _data.depth;
106 : }
107 : else {
108 : // other process performs infinite search
109 : maxDepth = MAX_DEPTH;
110 : currentMoveMs = INFINITETIME; // overrides currentMoveMs
111 : }
112 223 : _data.depth = 0; // reset depth as it will be used to return searched depth later
113 :
114 : // when limiting skill by nodes only, only apply that to main process
115 : // this doesn't make much sense to apply limited nodes on a multi-process run anyway ...
116 223 : if (Distributed::isMainProcess() && Skill::enabled() && DynamicConfig::nodesBasedLevel) {
117 0 : TimeMan::maxNodes = Skill::limitedNodes();
118 0 : Logging::LogIt(Logging::logDebug) << "Limited nodes to fit level: " << TimeMan::maxNodes;
119 : }
120 :
121 : // initialize some search variable
122 223 : moveDifficulty = MoveDifficultyUtil::MD_std;
123 223 : positionEvolution = MoveDifficultyUtil::PE_std;
124 223 : startTime = Clock::now();
125 :
126 : // check game history for potential situations (boom/moob)
127 223 : if (isMainThread()) {
128 208 : if (isBooming(p.halfmoves))
129 277 : positionEvolution = stack[p.halfmoves-2].eval > MoveDifficultyUtil::emergencyAttackThreshold ? MoveDifficultyUtil::PE_boomingAttack :
130 : MoveDifficultyUtil::PE_boomingDefence;
131 35 : else if (isMoobing(p.halfmoves))
132 32 : positionEvolution = stack[p.halfmoves-2].eval > MoveDifficultyUtil::emergencyAttackThreshold ? MoveDifficultyUtil::PE_moobingAttack :
133 : MoveDifficultyUtil::PE_moobingDefence;
134 : }
135 :
136 : // Main thread only will reset tables
137 223 : if (isMainThread()) {
138 208 : TT::age();
139 208 : MoveDifficultyUtil::variability = 1.f; // not usefull for co-searcher threads that won't depend on time
140 208 : ThreadPool::instance().clearSearch(); // reset tables for all threads !
141 : }
142 223 : if (isMainThread() || id() >= MAX_THREADS) {
143 208 : Logging::LogIt(Logging::logInfo) << "Search params :";
144 416 : Logging::LogIt(Logging::logInfo) << "requested time " << getCurrentMoveMs() << " (" << currentMoveMs << ")"; // won't exceed TimeMan::maxTime
145 416 : Logging::LogIt(Logging::logInfo) << "requested depth " << static_cast<int>(maxDepth);
146 : }
147 : // other threads will wait here for start signal
148 : else {
149 30 : Logging::LogIt(Logging::logInfo) << "helper thread waiting ... " << id();
150 1013116 : while (startLock.load()) { ; }
151 30 : Logging::LogIt(Logging::logInfo) << "... go for id " << id();
152 : }
153 :
154 : // fill "root" position stack data
155 : {
156 223 : EvalData eData;
157 223 : ScoreType e = eval(p, eData, *this, true);
158 223 : assert(p.halfmoves < MAX_PLY && p.halfmoves >= 0);
159 446 : stack[p.halfmoves] = {p, computeHash(p), /*eData,*/ e, INVALIDMINIMOVE};
160 : }
161 :
162 : // reset output search results
163 : _data.reset();
164 :
165 223 : const bool isInCheck = isPosInCheck(p);
166 :
167 : // initialize multiPV stuff
168 223 : DynamicConfig::multiPV = (Logging::ct == Logging::CT_uci ? DynamicConfig::multiPV : 1);
169 224 : if (Skill::enabled() && !DynamicConfig::nodesBasedLevel) { DynamicConfig::multiPV = std::max(DynamicConfig::multiPV, 4u); }
170 223 : Logging::LogIt(Logging::logInfo) << "MultiPV " << DynamicConfig::multiPV;
171 223 : std::vector<MultiPVScores> multiPVMoves(DynamicConfig::multiPV, {INVALIDMOVE, matedScore(0), {}, 0});
172 : // in multipv mode _data.score cannot be use a the aspiration loop score
173 223 : std::vector<ScoreType> currentScore(DynamicConfig::multiPV, 0);
174 :
175 : // handle "maxNodes" style search (will always complete depth 1 search)
176 223 : const auto maxNodes = TimeMan::maxNodes;
177 : // reset this for depth 1 to be sure to iterate at least once ...
178 : // on main process, requested value will be restored, but not on other process
179 223 : TimeMan::maxNodes = 0;
180 :
181 : double EBF = 1.7;
182 :
183 : // using MAX_DEPTH-6 so that draw can be found for sure ///@todo I don't understand this -6 anymore ..
184 223 : const DepthType targetMaxDepth = std::min(maxDepth, static_cast<DepthType>(MAX_DEPTH - 6));
185 :
186 223 : const bool isFiniteTimeSearch = maxNodes == 0 && !depthLimitedSearch && currentMoveMs < INFINITETIME && TimeMan::msecUntilNextTC > 0 && !getData().isPondering && !getData().isAnalysis;
187 :
188 : // forced bongcloud
189 223 : if (DynamicConfig::bongCloud && (p.castling & (p.c == Co_White ? C_w_all : C_b_all)) ){
190 0 : constexpr array1d<Move,5> wbc = { ToMove(Sq_e1,Sq_e2,T_std), ToMove(Sq_e1,Sq_d1,T_std), ToMove(Sq_e1,Sq_f1,T_std), ToMove(Sq_e1,Sq_d2,T_std), ToMove(Sq_e1,Sq_f2,T_std)};
191 0 : constexpr array1d<Move,5> bbc = { ToMove(Sq_e8,Sq_e7,T_std), ToMove(Sq_e8,Sq_d8,T_std), ToMove(Sq_e8,Sq_f8,T_std), ToMove(Sq_e8,Sq_d7,T_std), ToMove(Sq_e8,Sq_f7,T_std)};
192 0 : MoveList moves;
193 0 : MoveGen::generate(p,moves);
194 0 : for (int i = 0 ; i < 5; ++i){
195 0 : const Move m = p.c == Co_White ? wbc[i] : bbc[i];
196 0 : for (const auto & it : moves){
197 0 : if (sameMove(m,it)){
198 0 : _data.score = 0;
199 0 : _data.pv.emplace_back(m);
200 0 : goto pvsout;
201 : }
202 : }
203 : }
204 : }
205 :
206 : // random mover can be forced for the few first moves of a game or by setting level to 0
207 223 : if (DynamicConfig::level == 0 || p.halfmoves < DynamicConfig::randomPly || currentMoveMs <= TimeMan::msecMinimal) {
208 0 : if (p.halfmoves < DynamicConfig::randomPly) Logging::LogIt(Logging::logInfo) << "Randomized ply";
209 0 : _data.score = randomMover(p, _data.pv, isInCheck);
210 0 : goto pvsout;
211 : }
212 :
213 : // forced move detection
214 : // only main thread here (stopflag will be triggered anyway for other threads if needed)
215 223 : if (!Distributed::moreThanOneProcess() && isMainThread() && DynamicConfig::multiPV == 1 && isFiniteTimeSearch && currentMoveMs > 100) { ///@todo should work with nps here
216 1 : _data.score = pvs<true>(matedScore(0), matingScore(0), p, 1, 0, _data.pv, _data.seldepth, 0, isInCheck, false); // depth 1 search to get real valid moves
217 : // only one : check evasion or zugzwang
218 1 : if (rootScores.size() == 1) {
219 0 : moveDifficulty = MoveDifficultyUtil::MD_forced;
220 : }
221 : }
222 :
223 : // ID loop
224 1301 : for (DepthType depth = 1; depth <= targetMaxDepth && !stopFlag; ++depth) {
225 :
226 : // MultiPV loop
227 1078 : std::vector<MiniMove> skipMoves;
228 2173 : for (unsigned int multi = 0; multi < DynamicConfig::multiPV && !stopFlag; ++multi) {
229 : // No need to continue multiPV loop if a mate is found
230 1095 : if (!skipMoves.empty() && isMatedScore(currentScore[multi])) break;
231 :
232 1095 : if (isMainThread()) {
233 1086 : if (depth > 1) {
234 : // delayed other thread start (can use a depth condition...)
235 869 : if (startLock.load()) {
236 201 : Logging::LogIt(Logging::logInfo) << "Unlocking other threads";
237 : startLock.store(false);
238 : }
239 : }
240 : }
241 : // stockfish like thread management (not for co-searcher)
242 9 : else if (!subSearch) {
243 9 : const auto i = (id() - 1) % threadSkipSize;
244 9 : if (((depth + skipPhase[i]) / skipSize[i]) % 2){
245 4 : Logging::LogIt(Logging::logInfo) << "Thread " << id() << " skipping depth " << static_cast<int>(depth);
246 2 : continue; // next depth
247 : }
248 : }
249 :
250 3279 : Logging::LogIt(Logging::logInfo) << "Thread " << id() << " searching depth " << static_cast<int>(depth);
251 :
252 : #ifndef WITH_EVAL_TUNING
253 1093 : contempt = {ScoreType((p.c == Co_White ? +1 : -1) * (DynamicConfig::contempt + DynamicConfig::contemptMG) * DynamicConfig::ratingFactor),
254 1093 : ScoreType((p.c == Co_White ? +1 : -1) * DynamicConfig::contempt * DynamicConfig::ratingFactor)};
255 : #else
256 : contempt = 0;
257 : #endif
258 : // dynamic contempt ///@todo tune this
259 1093 : if (contempt[EG] >= 0){
260 608 : contempt += static_cast<ScoreType>(std::round(25 * std::tanh(currentScore[multi] / 400.f))); // slow but ok here
261 : }
262 2186 : Logging::LogIt(Logging::logInfo) << "Dynamic contempt " << contempt;
263 :
264 : // initialize aspiration window loop variables
265 1093 : PVList pvLoc;
266 : ScoreType delta =
267 1093 : (SearchConfig::doWindow && depth > SearchConfig::aspirationMinDepth)
268 266 : ? SearchConfig::aspirationInit + std::max(0, SearchConfig::aspirationDepthInit - SearchConfig::aspirationDepthCoef * depth)
269 1359 : : matingScore(0); // MATE not INFSCORE in order to enter the loop below once
270 1093 : ScoreType alpha = std::max(static_cast<ScoreType>(currentScore[multi] - delta), matedScore(0));
271 1414 : ScoreType beta = std::min(static_cast<ScoreType>(currentScore[multi] + delta), matingScore(0));
272 : ScoreType score = 0;
273 : DepthType windowDepth = depth;
274 1093 : Logging::LogIt(Logging::logInfo) << "Inital window: " << alpha << ".." << beta;
275 :
276 : // Aspiration loop
277 1370 : while (!stopFlag) {
278 : pvLoc.clear();
279 1384 : score = pvs<true>(alpha, beta, p, windowDepth, 0, pvLoc, _data.seldepth, 0, isInCheck, false, skipMoves.empty() ? nullptr : &skipMoves);
280 1369 : if (stopFlag) break;
281 1368 : ScoreType matW = 0;
282 1368 : ScoreType matB = 0;
283 1368 : delta += static_cast<ScoreType>((delta / 4) * std::exp(1.f - gamePhase(p.mat,matW,matB))); // in end-game, open window faster
284 2736 : if (delta > std::max(128, 1024*4/depth) ) delta = matingScore(0);
285 1368 : if (alpha > matedScore(0) && score <= alpha) {
286 : windowDepth = depth;
287 120 : beta = std::min(matingScore(0), static_cast<ScoreType>((alpha + beta) / 2));
288 120 : alpha = std::max(static_cast<ScoreType>(score - delta), matedScore(0));
289 120 : Logging::LogIt(Logging::logInfo) << "Increase window alpha " << alpha << ".." << beta;
290 120 : if (isMainThread() && DynamicConfig::multiPV == 1) {
291 120 : PVList pv2;
292 120 : TT::getPV(p, *this, pv2);
293 120 : displayGUI(depth, _data.seldepth, score, p.halfmoves, pv2, multi + 1, "?");
294 120 : }
295 : }
296 1248 : else if (beta < matingScore(0) && score >= beta) {
297 157 : --windowDepth; // from Ethereal
298 160 : beta = std::min(static_cast<ScoreType>(score + delta), matingScore(0));
299 157 : Logging::LogIt(Logging::logInfo) << "Increase window beta " << alpha << ".." << beta;
300 157 : if (isMainThread() && DynamicConfig::multiPV == 1) {
301 157 : PVList pv2;
302 157 : TT::getPV(p, *this, pv2);
303 157 : displayGUI(depth, _data.seldepth, score, p.halfmoves, pv2, multi + 1, "!");
304 157 : }
305 : }
306 : else
307 : break;
308 :
309 : } // Aspiration loop end
310 :
311 1093 : if (stopFlag) {
312 2 : if (multi != 0 && isMainThread()) {
313 : // handle multiPV display only based on previous ID iteration data
314 0 : displayGUI(depth - 1, multiPVMoves[multi].seldepth, multiPVMoves[multi].s, p.halfmoves, multiPVMoves[multi].pv, multi + 1);
315 : }
316 : }
317 : else {
318 : // this aspiration multipv loop was fully done, let's update results
319 1091 : _data.depth = depth;
320 1091 : currentScore[multi] = score;
321 :
322 : // In multiPV mode, fill skipmove for next multiPV iteration
323 1091 : if (!pvLoc.empty() && DynamicConfig::multiPV > 1) {
324 21 : skipMoves.emplace_back(Move2MiniMove(pvLoc[0]));
325 : }
326 :
327 : // backup multiPV info
328 1091 : if (!pvLoc.empty()){
329 1085 : multiPVMoves[multi].m = pvLoc[0];
330 : }
331 1091 : multiPVMoves[multi].s = score;
332 1091 : multiPVMoves[multi].pv = pvLoc;
333 1091 : multiPVMoves[multi].seldepth = _data.seldepth;
334 :
335 : // update the outputed pv only with the best move line
336 1091 : if (multi == 0) {
337 1076 : std::unique_lock lock(_mutexPV);
338 1076 : _data.pv = pvLoc;
339 1076 : _data.depth = depth;
340 1076 : _data.score = score;
341 : }
342 :
343 1091 : if (isMainThread()) {
344 : // output to GUI
345 2172 : displayGUI(depth, _data.seldepth, multiPVMoves[multi].s, p.halfmoves, pvLoc, multi + 1);
346 : }
347 :
348 1091 : if (isMainThread() && multi == 0) {
349 : // store current depth info
350 1074 : getSearchData().scores[depth] = _data.score;
351 1074 : getSearchData().nodes[depth] = ThreadPool::instance().counter(Stats::sid_nodes) + ThreadPool::instance().counter(Stats::sid_qnodes);
352 1074 : if (!pvLoc.empty()) getSearchData().moves[depth] = Move2MiniMove(pvLoc[0]);
353 :
354 : // check for an emergency :
355 : // if IID reports decreasing score, we have to take action (like search for longer)
356 0 : if (TimeMan::isDynamic &&
357 0 : depth > MoveDifficultyUtil::emergencyMinDepth &&
358 1074 : moveDifficulty == MoveDifficultyUtil::MD_std &&
359 0 : _data.score < getSearchData().scores[depth - 2] - MoveDifficultyUtil::emergencyMargin) {
360 0 : moveDifficulty = _data.score > MoveDifficultyUtil::emergencyAttackThreshold ? MoveDifficultyUtil::MD_moobAttackIID
361 : : MoveDifficultyUtil::MD_moobDefenceIID;
362 0 : Logging::LogIt(Logging::logInfo) << "Emergency mode activated : " << _data.score << " < "
363 0 : << getSearchData().scores[depth - 2] - MoveDifficultyUtil::emergencyMargin;
364 : }
365 :
366 : // update a "variability" measure to scale remaining time on it ///@todo tune this more
367 1074 : if (depth > 12 && !pvLoc.empty()) {
368 26 : if (/*getSearchData().moves[depth] != getSearchData().moves[depth - 2] &&*/
369 26 : std::fabs(getSearchData().scores[depth] - getSearchData().scores[depth - 2]) > MoveDifficultyUtil::emergencyMargin/4 )
370 14 : MoveDifficultyUtil::variability *= (1.f + depth/100.f);
371 : else
372 12 : MoveDifficultyUtil::variability *= 0.98f;
373 26 : Logging::LogIt(Logging::logInfo) << "Variability :" << MoveDifficultyUtil::variability;
374 26 : Logging::LogIt(Logging::logInfo) << "Variability time factor :" << MoveDifficultyUtil::variabilityFactor();
375 : }
376 :
377 : // check for remaining time
378 1074 : if (TimeMan::isDynamic && static_cast<TimeType>(static_cast<double>(getTimeDiff(startTime))*1.2*EBF) > getCurrentMoveMs()) {
379 0 : stopFlag = true;
380 0 : Logging::LogIt(Logging::logInfo) << "stopflag triggered, not enough time for next depth";
381 : break;
382 : }
383 :
384 : // compute EBF
385 1074 : if (depth > 12) {
386 52 : EBF = getSearchData().nodes[depth] / static_cast<double>(asLeastOne(getSearchData().nodes[depth - 1]));
387 26 : Logging::LogIt(Logging::logInfo) << "EBF " << EBF;
388 26 : Logging::LogIt(Logging::logInfo) << "EBF2 "
389 26 : << ThreadPool::instance().counter(Stats::sid_qnodes) /
390 52 : static_cast<double>(asLeastOne(ThreadPool::instance().counter(Stats::sid_nodes)));
391 : }
392 :
393 : // sync (pull) stopflag in other process
394 : if (!Distributed::isMainProcess()) {
395 : bool masterStopFlag;
396 : Distributed::get(&masterStopFlag, 1, Distributed::_winStopFromR0, 0);
397 : ThreadPool::instance().main().stopFlag = masterStopFlag;
398 : }
399 : }
400 : }
401 1093 : } // multiPV loop end
402 :
403 : // check for a node count stop
404 1078 : if (isMainThread() || isStoppableCoSearcher) {
405 : // restore real value (only on main processus!), was discarded for depth 1 search
406 1074 : if (Distributed::isMainProcess()) TimeMan::maxNodes = maxNodes;
407 2148 : const Counter nodeCount = isStoppableCoSearcher ? stats.counters[Stats::sid_nodes] + stats.counters[Stats::sid_qnodes]
408 1074 : : ThreadPool::instance().counter(Stats::sid_nodes) + ThreadPool::instance().counter(Stats::sid_qnodes);
409 1074 : if (TimeMan::maxNodes > 0 && nodeCount > TimeMan::maxNodes) {
410 0 : stopFlag = true;
411 0 : Logging::LogIt(Logging::logInfo) << "stopFlag triggered in search driver (nodes limits) in thread " << id();
412 : }
413 : }
414 :
415 : } // iterative deepening loop end
416 :
417 223 : pvsout:
418 :
419 : // for all thread, best move is set to first pv move (if there is one ...)
420 : // this will be changed later for main thread to take skill or best move into account
421 223 : if (_data.pv.empty()) _data.best = INVALIDMOVE;
422 207 : else _data.best = _data.pv[0];
423 :
424 223 : if (isMainThread()) {
425 : // in case of very very short depth or time, "others" threads may still be blocked
426 208 : Logging::LogIt(Logging::logInfo) << "Unlocking other threads (end of search)";
427 : startLock.store(false);
428 :
429 : // all threads are updating their output values but main one is looking for the longest pv
430 : // note that depth, score, seldepth and pv are already updated on-the-fly
431 208 : if (_data.pv.empty()) {
432 5 : if (!subSearch) Logging::LogIt(Logging::logWarn) << "Empty pv";
433 : }
434 : else {
435 : // !!! warning: when skill uses multiPV, returned move shall be used and not first move of pv in receiveMoves !!!
436 205 : if (Skill::enabled() && !DynamicConfig::nodesBasedLevel) { _data.best = Skill::pick(multiPVMoves); }
437 : else {
438 : // get pv from best (deepest) threads
439 201 : DepthType bestDepth = _data.depth;
440 : size_t bestThreadId = 0;
441 402 : for (const auto& s : ThreadPool::instance()) {
442 201 : std::unique_lock lock(_mutexPV);
443 201 : if (s->getData().depth > bestDepth) {
444 0 : bestThreadId = s->id();
445 0 : bestDepth = s->getData().depth;
446 0 : Logging::LogIt(Logging::logInfo) << "Better thread ! " << bestThreadId << ", depth " << static_cast<int>(bestDepth);
447 : }
448 : }
449 : // update data with best data available
450 201 : _data = ThreadPool::instance()[bestThreadId]->getData();
451 201 : _data.best = _data.pv[0]; ///@todo this can lead to best move not being coherent with last reported PV
452 : }
453 : // update stack data on all searcher with "real" score
454 : // this way all stack[k (with k < p.halfmove)] will be "history" of the game accessible to searcher
455 422 : for (auto& s : ThreadPool::instance()) {
456 217 : s->stack[p.halfmoves].eval = _data.score;
457 : }
458 : }
459 :
460 : // wait for "ponderhit" or "stop" in case search returned too soon
461 208 : if (!stopFlag && (getData().isPondering || getData().isAnalysis)) {
462 0 : Logging::LogIt(Logging::logInfo) << "Waiting for ponderhit or stop ...";
463 0 : while (!stopFlag && (getData().isPondering || getData().isAnalysis)) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); }
464 0 : Logging::LogIt(Logging::logInfo) << "... ok";
465 : }
466 :
467 : // now send stopflag to all threads
468 208 : ThreadPool::instance().stop();
469 : // and wait for them
470 208 : ThreadPool::instance().wait(true);
471 :
472 : // gather various process stats
473 : Distributed::syncStat();
474 : // share our TT
475 : Distributed::syncTT();
476 :
477 : // display search statistics (only when all threads and process are done and sync)
478 : if (Distributed::moreThanOneProcess()) { Distributed::showStat(); }
479 : else {
480 208 : ThreadPool::instance().displayStats();
481 : }
482 :
483 : // report energy consumption for this search
484 208 : if (auto* monitor = getEnergyMonitor()) {
485 : const TimeType searchDuration = getTimeDiff(startTime);
486 208 : monitor->reportEnergy(searchDuration, Logging::logInfoPrio);
487 208 : monitor->reportCost(searchDuration, Logging::logInfoPrio);
488 : }
489 :
490 208 : if (postMove) {
491 : // send move and ponder move to GUI
492 208 : const bool success = COM::receiveMoves(_data.best, _data.pv.size() > 1 && !(Skill::enabled() && !DynamicConfig::nodesBasedLevel) ? _data.pv[1] : INVALIDMOVE);
493 208 : if ( COM::protocol == COM::p_xboard){
494 : // update position state and history (this is only a xboard need)
495 2 : XBoard::moveApplied(success, _data.best);
496 : }
497 : }
498 :
499 416 : Distributed::sync(Distributed::_commMove, "end of search driver");
500 : } // isMainThread()
501 :
502 446 : Logging::LogIt(Logging::logInfoPrio) << "End of search driver for thread " << id();
503 :
504 223 : _searching = false;
505 669 : }
|