X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=src%2Fsearch.cpp;h=43a3175bfe83e309b219f116c8bcc3aded3a0d1b;hb=856a5f3aaaf8b9d53599963decacd4476b55c034;hp=e773a36a72af9b40915dfede7c9a79ed45a9c5b1;hpb=f6f1d2422303923927c0c088dee1d6df22dc4b98;p=stockfish diff --git a/src/search.cpp b/src/search.cpp index e773a36a..43a3175b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -105,14 +105,22 @@ namespace { string uci_pv(const Position& pos, Depth depth, Value alpha, Value beta); struct Skill { - Skill(int l) : level(l) {} - bool enabled() const { return level < 20; } + Skill(int l, size_t rootSize) : level(l), + candidates(l < 20 ? std::min(4, (int)rootSize) : 0), + best(MOVE_NONE) {} + ~Skill() { + if (candidates) // Swap best PV line with the sub-optimal one + std::swap(RootMoves[0], *std::find(RootMoves.begin(), + RootMoves.end(), best ? best : pick_move())); + } + + size_t candidates_size() const { return candidates; } bool time_to_pick(Depth depth) const { return depth / ONE_PLY == 1 + level; } - Move best_move(size_t multiPV) { return best ? best : pick_best(multiPV); } - Move pick_best(size_t multiPV); + Move pick_move(); int level; - Move best = MOVE_NONE; + size_t candidates; + Move best; }; } // namespace @@ -159,19 +167,19 @@ uint64_t Search::perft(Position& pos, Depth depth) { CheckInfo ci(pos); const bool leaf = (depth == 2 * ONE_PLY); - for (const ExtMove& ms : MoveList(pos)) + for (MoveList it(pos); *it; ++it) { if (Root && depth <= ONE_PLY) cnt = 1, nodes++; else { - pos.do_move(ms.move, st, ci, pos.gives_check(ms.move, ci)); + pos.do_move(*it, st, pos.gives_check(*it, ci)); cnt = leaf ? MoveList(pos).size() : perft(pos, depth - ONE_PLY); nodes += cnt; - pos.undo_move(ms.move); + pos.undo_move(*it); } if (Root) - sync_cout << UCI::move(ms.move, pos.is_chess960()) << ": " << cnt << sync_endl; + sync_cout << UCI::move(*it, pos.is_chess960()) << ": " << cnt << sync_endl; } return nodes; } @@ -244,8 +252,8 @@ void Search::think() { } } - for (Thread* th : Threads) - th->maxPly = 0; + for (size_t i = 0; i < Threads.size(); ++i) + Threads[i]->maxPly = 0; Threads.timer->run = true; Threads.timer->notify_one(); // Wake up the recurring timer @@ -301,14 +309,11 @@ namespace { Followupmoves.clear(); size_t multiPV = Options["MultiPV"]; - Skill skill(Options["Skill Level"]); - - // When playing with strength handicap enable MultiPV search that we will - // use behind the scenes to retrieve a set of possible moves. - if (skill.enabled()) - multiPV = std::max(multiPV, (size_t)4); + Skill skill(Options["Skill Level"], RootMoves.size()); - multiPV = std::min(multiPV, RootMoves.size()); + // Do we have to play with skill handicap? In this case enable MultiPV search + // that we will use behind the scenes to retrieve a set of possible moves. + multiPV = std::max(multiPV, skill.candidates_size()); // Iterative deepening loop until requested to stop or target depth reached while (++depth < DEPTH_MAX && !Signals.stop && (!Limits.depth || depth <= Limits.depth)) @@ -318,11 +323,11 @@ namespace { // Save the last iteration's scores before first PV line is searched and // all the move scores except the (new) PV are set to -VALUE_INFINITE. - for (RootMove& rm : RootMoves) - rm.previousScore = rm.score; + for (size_t i = 0; i < RootMoves.size(); ++i) + RootMoves[i].previousScore = RootMoves[i].score; // MultiPV loop. We perform a full root search for each PV line - for (PVIdx = 0; PVIdx < multiPV && !Signals.stop; ++PVIdx) + for (PVIdx = 0; PVIdx < std::min(multiPV, RootMoves.size()) && !Signals.stop; ++PVIdx) { // Reset aspiration window starting size if (depth >= 5 * ONE_PLY) @@ -395,13 +400,14 @@ namespace { sync_cout << "info nodes " << RootPos.nodes_searched() << " time " << Time::now() - SearchTime << sync_endl; - else if (PVIdx + 1 == multiPV || Time::now() - SearchTime > 3000) + else if ( PVIdx + 1 == std::min(multiPV, RootMoves.size()) + || Time::now() - SearchTime > 3000) sync_cout << uci_pv(pos, depth, alpha, beta) << sync_endl; } - // If skill level is enabled and time is up, pick a sub-optimal best move - if (skill.enabled() && skill.time_to_pick(depth)) - skill.pick_best(multiPV); + // If skill levels are enabled and time is up, pick a sub-optimal best move + if (skill.candidates_size() && skill.time_to_pick(depth)) + skill.pick_move(); // Have we found a "mate in x"? if ( Limits.mate @@ -430,11 +436,6 @@ namespace { } } } - - // If skill level is enabled, swap best PV line with the sub-optimal one - if (skill.enabled()) - std::swap(RootMoves[0], *std::find(RootMoves.begin(), - RootMoves.end(), skill.best_move(multiPV))); } @@ -476,7 +477,7 @@ namespace { splitPoint = ss->splitPoint; bestMove = splitPoint->bestMove; bestValue = splitPoint->bestValue; - tte = nullptr; + tte = NULL; ttHit = false; ttMove = excludedMove = MOVE_NONE; ttValue = VALUE_NONE; @@ -539,7 +540,7 @@ namespace { // If ttMove is quiet, update killers, history, counter move and followup move on TT hit if (ttValue >= beta && ttMove && !pos.capture_or_promotion(ttMove) && !inCheck) - update_stats(pos, ss, ttMove, depth, nullptr, 0); + update_stats(pos, ss, ttMove, depth, NULL, 0); return ttValue; } @@ -701,7 +702,7 @@ namespace { if (pos.legal(move, ci.pinned)) { ss->currentMove = move; - pos.do_move(move, st, ci, pos.gives_check(move, ci)); + pos.do_move(move, st, pos.gives_check(move, ci)); value = -search(pos, ss+1, -rbeta, -rbeta+1, rdepth, !cutNode); pos.undo_move(move); if (value >= rbeta) @@ -788,7 +789,7 @@ moves_loop: // When in check and at SpNode search starts from here } if (PvNode) - (ss+1)->pv = nullptr; + (ss+1)->pv = NULL; extension = DEPTH_ZERO; captureOrPromotion = pos.capture_or_promotion(move); @@ -893,7 +894,7 @@ moves_loop: // When in check and at SpNode search starts from here quietsSearched[quietCount++] = move; // Step 14. Make the move - pos.do_move(move, st, ci, givesCheck); + pos.do_move(move, st, givesCheck); // Step 15. Reduced depth search (LMR). If the move fails high it will be // re-searched at full depth. @@ -1041,7 +1042,9 @@ moves_loop: // When in check and at SpNode search starts from here && Threads.size() >= 2 && depth >= Threads.minimumSplitDepth && ( !thisThread->activeSplitPoint - || !thisThread->activeSplitPoint->allSlavesSearching) + || !thisThread->activeSplitPoint->allSlavesSearching + || ( Threads.size() > MAX_SLAVES_PER_SPLITPOINT + && thisThread->activeSplitPoint->slavesMask.count() == MAX_SLAVES_PER_SPLITPOINT)) && thisThread->splitPointsSize < MAX_SPLITPOINTS_PER_THREAD) { assert(bestValue > -VALUE_INFINITE && bestValue < beta); @@ -1254,7 +1257,7 @@ moves_loop: // When in check and at SpNode search starts from here ss->currentMove = move; // Make and search the move - pos.do_move(move, st, ci, givesCheck); + pos.do_move(move, st, givesCheck); value = givesCheck ? -qsearch(pos, ss+1, -beta, -alpha, depth - ONE_PLY) : -qsearch(pos, ss+1, -beta, -alpha, depth - ONE_PLY); pos.undo_move(move); @@ -1371,23 +1374,24 @@ moves_loop: // When in check and at SpNode search starts from here } - // When playing with strength handicap, choose best move among a set of RootMoves - // using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. + // When playing with a strength handicap, choose best move among the first 'candidates' + // RootMoves using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. - Move Skill::pick_best(size_t multiPV) { + Move Skill::pick_move() { // PRNG sequence should be non-deterministic, so we seed it with the time at init static PRNG rng(Time::now()); // RootMoves are already sorted by score in descending order - int variance = std::min(RootMoves[0].score - RootMoves[multiPV - 1].score, PawnValueMg); + int variance = std::min(RootMoves[0].score - RootMoves[candidates - 1].score, PawnValueMg); int weakness = 120 - 2 * level; int maxScore = -VALUE_INFINITE; + best = MOVE_NONE; // Choose best move. For each move score we add two terms both dependent on - // weakness. One deterministic and bigger for weaker levels, and one random, + // weakness. One deterministic and bigger for weaker moves, and one random, // then we choose the move with the resulting highest score. - for (size_t i = 0; i < multiPV; ++i) + for (size_t i = 0; i < candidates; ++i) { int score = RootMoves[i].score; @@ -1416,9 +1420,9 @@ moves_loop: // When in check and at SpNode search starts from here size_t uciPVSize = std::min((size_t)Options["MultiPV"], RootMoves.size()); int selDepth = 0; - for (Thread* th : Threads) - if (th->maxPly > selDepth) - selDepth = th->maxPly; + for (size_t i = 0; i < Threads.size(); ++i) + if (Threads[i]->maxPly > selDepth) + selDepth = Threads[i]->maxPly; for (size_t i = 0; i < uciPVSize; ++i) { @@ -1445,8 +1449,12 @@ moves_loop: // When in check and at SpNode search starts from here ss << (v >= beta ? " lowerbound" : v <= alpha ? " upperbound" : ""); ss << " nodes " << pos.nodes_searched() - << " nps " << pos.nodes_searched() * 1000 / elapsed - << " tbhits " << TB::Hits + << " nps " << pos.nodes_searched() * 1000 / elapsed; + + if (elapsed > 1000) // Earlier makes little sense + ss << " hashfull " << TT.hashfull(); + + ss << " tbhits " << TB::Hits << " time " << elapsed << " pv"; @@ -1516,9 +1524,9 @@ void Thread::idle_loop() { // Pointer 'this_sp' is not null only if we are called from split(), and not // at the thread creation. This means we are the split point's master. - SplitPoint* this_sp = splitPointsSize ? activeSplitPoint : nullptr; + SplitPoint* this_sp = activeSplitPoint; - assert(!this_sp || (this_sp->masterThread == this && searching)); + assert(!this_sp || (this_sp->master == this && searching)); while (!exit) { @@ -1528,6 +1536,7 @@ void Thread::idle_loop() { Threads.mutex.lock(); assert(activeSplitPoint); + SplitPoint* sp = activeSplitPoint; Threads.mutex.unlock(); @@ -1540,7 +1549,7 @@ void Thread::idle_loop() { sp->mutex.lock(); - assert(activePosition == nullptr); + assert(activePosition == NULL); activePosition = &pos; @@ -1559,18 +1568,18 @@ void Thread::idle_loop() { assert(searching); searching = false; - activePosition = nullptr; + activePosition = NULL; sp->slavesMask.reset(idx); sp->allSlavesSearching = false; sp->nodes += pos.nodes_searched(); // Wake up the master thread so to allow it to return from the idle // loop in case we are the last slave of the split point. - if ( this != sp->masterThread - && sp->slavesMask.none()) + if (this != sp->master && sp->slavesMask.none()) { - assert(!sp->masterThread->searching); - sp->masterThread->notify_one(); + assert(!sp->master->searching); + + sp->master->notify_one(); } // After releasing the lock we can't access any SplitPoint related data @@ -1580,50 +1589,76 @@ void Thread::idle_loop() { // Try to late join to another split point if none of its slaves has // already finished. - if (Threads.size() > 2) - for (size_t i = 0; i < Threads.size(); ++i) + SplitPoint* bestSp = NULL; + int minLevel = INT_MAX; + + for (size_t i = 0; i < Threads.size(); ++i) + { + const size_t size = Threads[i]->splitPointsSize; // Local copy + sp = size ? &Threads[i]->splitPoints[size - 1] : NULL; + + if ( sp + && sp->allSlavesSearching + && sp->slavesMask.count() < MAX_SLAVES_PER_SPLITPOINT + && available_to(Threads[i])) { - const int size = Threads[i]->splitPointsSize; // Local copy - sp = size ? &Threads[i]->splitPoints[size - 1] : nullptr; + assert(this != Threads[i]); + assert(!(this_sp && this_sp->slavesMask.none())); + assert(Threads.size() > 2); - if ( sp - && sp->allSlavesSearching - && available_to(Threads[i])) + // Prefer to join to SP with few parents to reduce the probability + // that a cut-off occurs above us, and hence we waste our work. + int level = 0; + for (SplitPoint* p = Threads[i]->activeSplitPoint; p; p = p->parentSplitPoint) + level++; + + if (level < minLevel) { - // Recheck the conditions under lock protection - Threads.mutex.lock(); - sp->mutex.lock(); - - if ( sp->allSlavesSearching - && available_to(Threads[i])) - { - sp->slavesMask.set(idx); - activeSplitPoint = sp; - searching = true; - } - - sp->mutex.unlock(); - Threads.mutex.unlock(); - - break; // Just a single attempt + bestSp = sp; + minLevel = level; } } + } + + if (bestSp) + { + sp = bestSp; + + // Recheck the conditions under lock protection + Threads.mutex.lock(); + sp->mutex.lock(); + + if ( sp->allSlavesSearching + && sp->slavesMask.count() < MAX_SLAVES_PER_SPLITPOINT + && available_to(sp->master)) + { + sp->slavesMask.set(idx); + activeSplitPoint = sp; + searching = true; + } + + sp->mutex.unlock(); + Threads.mutex.unlock(); + } } - // Grab the lock to avoid races with Thread::notify_one() - std::unique_lock lk(mutex); + // Avoid races with notify_one() fired from last slave of the split point + mutex.lock(); // If we are master and all slaves have finished then exit idle_loop if (this_sp && this_sp->slavesMask.none()) { assert(!searching); + mutex.unlock(); break; } // If we are not searching, wait for a condition to be signaled instead of // wasting CPU time polling for work. if (!searching && !exit) - sleepCondition.wait(lk); + sleepCondition.wait(mutex); + + mutex.unlock(); } } @@ -1668,10 +1703,10 @@ void check_time() { // Loop across all split points and sum accumulated SplitPoint nodes plus // all the currently active positions nodes. - for (Thread* th : Threads) - for (int i = 0; i < th->splitPointsSize; ++i) + for (size_t i = 0; i < Threads.size(); ++i) + for (size_t j = 0; j < Threads[i]->splitPointsSize; ++j) { - SplitPoint& sp = th->splitPoints[i]; + SplitPoint& sp = Threads[i]->splitPoints[j]; sp.mutex.lock();