X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=src%2Fsearch.cpp;h=43a3175bfe83e309b219f116c8bcc3aded3a0d1b;hb=856a5f3aaaf8b9d53599963decacd4476b55c034;hp=a15f259e2b791df7014a51ac6089b8444860d081;hpb=f53aea45e3230239d358d4d35357c9ee6bf6fb54;p=stockfish diff --git a/src/search.cpp b/src/search.cpp index a15f259e..43a3175b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -167,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; } @@ -252,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 @@ -276,7 +276,7 @@ void Search::think() { sync_cout << "bestmove " << UCI::move(RootMoves[0].pv[0], RootPos.is_chess960()); - if (RootMoves[0].pv.size() > 1) + if (RootMoves[0].pv.size() > 1 || RootMoves[0].extract_ponder_from_tt(RootPos)) std::cout << " ponder " << UCI::move(RootMoves[0].pv[1], RootPos.is_chess960()); std::cout << sync_endl; @@ -323,8 +323,8 @@ 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 < std::min(multiPV, RootMoves.size()) && !Signals.stop; ++PVIdx) @@ -365,7 +365,8 @@ namespace { // When failing high/low give some update (without cluttering // the UI) before a re-search. - if ( (bestValue <= alpha || bestValue >= beta) + if ( multiPV == 1 + && (bestValue <= alpha || bestValue >= beta) && Time::now() - SearchTime > 3000) sync_cout << uci_pv(pos, depth, alpha, beta) << sync_endl; @@ -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); @@ -830,7 +831,8 @@ moves_loop: // When in check and at SpNode search starts from here newDepth = depth - ONE_PLY + extension; // Step 13. Pruning at shallow depth - if ( !captureOrPromotion + if ( !RootNode + && !captureOrPromotion && !inCheck && !dangerous && bestValue > VALUE_MATED_IN_MAX_PLY) @@ -892,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. @@ -1040,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); @@ -1253,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); @@ -1391,10 +1395,6 @@ moves_loop: // When in check and at SpNode search starts from here { int score = RootMoves[i].score; - // Don't allow crazy blunders even at very low skills - if (i > 0 && RootMoves[i - 1].score > score + 2 * PawnValueMg) - break; - // This is our magic formula score += ( weakness * int(RootMoves[0].score - score) + variance * (rng.rand() % weakness)) / 128; @@ -1420,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) { @@ -1449,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"; @@ -1490,15 +1494,39 @@ void RootMove::insert_pv_in_tt(Position& pos) { } +/// RootMove::extract_ponder_from_tt() is called in case we have no ponder move before +/// exiting the search, for instance in case we stop the search during a fail high at +/// root. We try hard to have a ponder move to return to the GUI, otherwise in case of +/// 'ponder on' we have nothing to think on. + +Move RootMove::extract_ponder_from_tt(Position& pos) +{ + StateInfo st; + bool found; + + assert(pv.size() == 1); + + pos.do_move(pv[0], st); + TTEntry* tte = TT.probe(pos.key(), found); + Move m = found ? tte->move() : MOVE_NONE; + if (!MoveList(pos).contains(m)) + m = MOVE_NONE; + + pos.undo_move(pv[0]); + pv.push_back(m); + return m; +} + + /// Thread::idle_loop() is where the thread is parked when it has no work to do 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) { @@ -1508,6 +1536,7 @@ void Thread::idle_loop() { Threads.mutex.lock(); assert(activeSplitPoint); + SplitPoint* sp = activeSplitPoint; Threads.mutex.unlock(); @@ -1520,7 +1549,7 @@ void Thread::idle_loop() { sp->mutex.lock(); - assert(activePosition == nullptr); + assert(activePosition == NULL); activePosition = &pos; @@ -1539,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 @@ -1560,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); + + // 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 ( sp - && sp->allSlavesSearching - && available_to(Threads[i])) + 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(); } } @@ -1648,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();