X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fsearch.cpp;h=30f280728e274055d49771ceb2a413d5b91aa9dd;hp=ba1fef4d07e8081a56c73e354c915f8bf037f894;hb=1982fe25f817c29aef9dd156869a53e520ce3629;hpb=acc47e8b79058a06508fee804aa428820e163b8b diff --git a/src/search.cpp b/src/search.cpp index ba1fef4d..30f28072 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -18,7 +18,6 @@ along with this program. If not, see . */ -#include #include #include #include // For std::memset @@ -61,10 +60,6 @@ namespace { // Different node types, used as a template parameter enum NodeType { NonPV, PV }; - // Sizes and phases of the skip-blocks, used for distributing search depths across the threads - constexpr int SkipSize[] = { 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4 }; - constexpr int SkipPhase[] = { 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7 }; - // Razor and futility margins constexpr int RazorMargin = 600; Value futility_margin(Depth d, bool improving) { @@ -89,11 +84,10 @@ namespace { return d > 17 ? 0 : 29 * d * d + 138 * d - 134; } - // Add a small random component to draw evaluations to keep search dynamic - // and to avoid 3fold-blindness. + // Add a small random component to draw evaluations to avoid 3fold-blindness Value value_draw(Depth depth, Thread* thisThread) { return depth < 4 ? VALUE_DRAW - : VALUE_DRAW + Value(2 * (thisThread->nodes.load(std::memory_order_relaxed) % 2) - 1); + : VALUE_DRAW + Value(2 * (thisThread->nodes & 1) - 1); } // Skill structure is used to implement strength limit @@ -120,13 +114,6 @@ namespace { void update_quiet_stats(const Position& pos, Stack* ss, Move move, Move* quiets, int quietCount, int bonus); void update_capture_stats(const Position& pos, Move move, Move* captures, int captureCount, int bonus); - inline bool gives_check(const Position& pos, Move move) { - Color us = pos.side_to_move(); - return type_of(move) == NORMAL && !(pos.blockers_for_king(~us) & pos.pieces(us)) - ? pos.check_squares(type_of(pos.moved_piece(move))) & to_sq(move) - : pos.gives_check(move); - } - // perft() is our utility to verify move generation. All the leaf nodes up // to the given depth are generated and counted, and the sum is returned. template @@ -174,12 +161,12 @@ void Search::clear() { Time.availableNodes = 0; TT.clear(); Threads.clear(); - Tablebases::init(Options["SyzygyPath"]); // Free up mapped files + Tablebases::init(Options["SyzygyPath"]); // Free mapped files } -/// MainThread::search() is called by the main thread when the program receives -/// the UCI 'go' command. It searches from the root position and outputs the "bestmove". +/// MainThread::search() is started when the program receives the UCI 'go' +/// command. It searches from the root position and outputs the "bestmove". void MainThread::search() { @@ -204,8 +191,11 @@ void MainThread::search() { else { for (Thread* th : Threads) + { + th->bestMoveChanges = 0; if (th != this) th->start_searching(); + } Thread::search(); // Let's start searching! } @@ -233,8 +223,9 @@ void MainThread::search() { if (Limits.npmsec) Time.availableNodes += Limits.inc[us] - Threads.nodes_searched(); - // Check if there are threads with a better score than main thread Thread* bestThread = this; + + // Check if there are threads with a better score than main thread if ( Options["MultiPV"] == 1 && !Limits.depth && !Skill(Options["Skill Level"]).enabled() @@ -285,9 +276,9 @@ void MainThread::search() { void Thread::search() { - // To allow access to (ss-5) up to (ss+2), the stack must be oversized. + // To allow access to (ss-7) up to (ss+2), the stack must be oversized. // The former is needed to allow update_continuation_histories(ss-1, ...), - // which accesses its argument at ss-4, also near the root. + // which accesses its argument at ss-6, also near the root. // The latter is needed for statScores and killer initialization. Stack stack[MAX_PLY+10], *ss = stack+7; Move pv[MAX_PLY+1]; @@ -295,7 +286,7 @@ void Thread::search() { Move lastBestMove = MOVE_NONE; Depth lastBestMoveDepth = DEPTH_ZERO; MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); - double timeReduction = 1.0; + double timeReduction = 1, totBestMoveChanges = 0; Color us = rootPos.side_to_move(); std::memset(ss-7, 0, 10 * sizeof(Stack)); @@ -306,9 +297,6 @@ void Thread::search() { bestValue = delta = alpha = -VALUE_INFINITE; beta = VALUE_INFINITE; - if (mainThread) - mainThread->bestMoveChanges = 0; - size_t multiPV = Options["MultiPV"]; Skill skill(Options["Skill Level"]); @@ -329,7 +317,7 @@ void Thread::search() { : Options["Analysis Contempt"] == "Black" && us == WHITE ? -ct : ct; - // In evaluate.cpp the evaluation is from the white point of view + // Evaluation score is from the white point of view contempt = (us == WHITE ? make_score(ct, ct / 2) : -make_score(ct, ct / 2)); @@ -338,17 +326,9 @@ void Thread::search() { && !Threads.stop && !(Limits.depth && mainThread && rootDepth / ONE_PLY > Limits.depth)) { - // Distribute search depths across the helper threads - if (idx > 0) - { - int i = (idx - 1) % 20; - if (((rootDepth / ONE_PLY + SkipPhase[i]) / SkipSize[i]) % 2) - continue; // Retry with an incremented rootDepth - } - // Age out PV variability metric if (mainThread) - mainThread->bestMoveChanges *= 0.517; + totBestMoveChanges /= 2; // 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. @@ -479,15 +459,18 @@ void Thread::search() { && !Threads.stop && !mainThread->stopOnPonderhit) { - double fallingEval = (306 + 9 * (mainThread->previousScore - bestValue)) / 581.0; - fallingEval = std::max(0.5, std::min(1.5, fallingEval)); + double fallingEval = (314 + 9 * (mainThread->previousScore - bestValue)) / 581.0; + fallingEval = clamp(fallingEval, 0.5, 1.5); // If the bestMove is stable over several iterations, reduce time accordingly timeReduction = lastBestMoveDepth + 10 * ONE_PLY < completedDepth ? 1.95 : 1.0; double reduction = std::pow(mainThread->previousTimeReduction, 0.528) / timeReduction; // Use part of the gained time from a previous stable move for the current move - double bestMoveInstability = 1.0 + mainThread->bestMoveChanges; + for (Thread* th : Threads) + totBestMoveChanges += th->bestMoveChanges; + + double bestMoveInstability = 1 + totBestMoveChanges / Threads.size(); // Stop the search if we have only one legal move, or if available time elapsed if ( rootMoves.size() == 1 @@ -553,7 +536,7 @@ namespace { Key posKey; Move ttMove, move, excludedMove, bestMove; Depth extension, newDepth; - Value bestValue, value, ttValue, eval, maxValue, pureStaticEval; + Value bestValue, value, ttValue, eval, maxValue; bool ttHit, ttPv, inCheck, givesCheck, improving; bool captureOrPromotion, doFullDepthSearch, moveCountPruning, ttCapture; Piece movedPiece; @@ -622,6 +605,15 @@ namespace { : ttHit ? tte->move() : MOVE_NONE; ttPv = (ttHit && tte->is_pv()) || (PvNode && depth > 4 * ONE_PLY); + // if position has been searched at higher depths and we are shuffling, return value_draw + if (pos.rule50_count() > 36 + && ss->ply > 36 + && depth < 3 * ONE_PLY + && ttHit + && tte->depth() > depth + && pos.count() > 0) + return VALUE_DRAW; + // At non-PV nodes we check for an early TT cutoff if ( !PvNode && ttHit @@ -708,16 +700,16 @@ namespace { // Step 6. Static evaluation of the position if (inCheck) { - ss->staticEval = eval = pureStaticEval = VALUE_NONE; + ss->staticEval = eval = VALUE_NONE; improving = false; goto moves_loop; // Skip early pruning when in check } else if (ttHit) { // Never assume anything on values stored in TT - ss->staticEval = eval = pureStaticEval = tte->eval(); + ss->staticEval = eval = tte->eval(); if (eval == VALUE_NONE) - ss->staticEval = eval = pureStaticEval = evaluate(pos); + ss->staticEval = eval = evaluate(pos); // Can ttValue be used as a better position evaluation? if ( ttValue != VALUE_NONE @@ -730,13 +722,12 @@ namespace { { int bonus = -(ss-1)->statScore / 512; - pureStaticEval = evaluate(pos); - ss->staticEval = eval = pureStaticEval + bonus; + ss->staticEval = eval = evaluate(pos) + bonus; } else - ss->staticEval = eval = pureStaticEval = -(ss-1)->staticEval + 2 * Eval::Tempo; + ss->staticEval = eval = -(ss-1)->staticEval + 2 * Eval::Tempo; - tte->save(posKey, VALUE_NONE, ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, pureStaticEval); + tte->save(posKey, VALUE_NONE, ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); } // Step 7. Razoring (~2 Elo) @@ -760,7 +751,7 @@ namespace { && (ss-1)->currentMove != MOVE_NULL && (ss-1)->statScore < 23200 && eval >= beta - && pureStaticEval >= beta - 36 * depth / ONE_PLY + 225 + && ss->staticEval >= beta - 36 * depth / ONE_PLY + 225 && !excludedMove && pos.non_pawn_material(us) && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) @@ -843,8 +834,7 @@ namespace { } // Step 11. Internal iterative deepening (~2 Elo) - if ( depth >= 8 * ONE_PLY - && !ttMove) + if (depth >= 8 * ONE_PLY && !ttMove) { search(pos, ss, alpha, beta, depth - 7 * ONE_PLY, cutNode); @@ -858,6 +848,7 @@ moves_loop: // When in check, search starts from here const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, nullptr, (ss-4)->continuationHistory, nullptr, (ss-6)->continuationHistory }; + Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq]; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, @@ -865,8 +856,8 @@ moves_loop: // When in check, search starts from here contHist, countermove, ss->killers); - value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc + value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc moveCountPruning = false; ttCapture = ttMove && pos.capture_or_promotion(ttMove); @@ -899,7 +890,7 @@ moves_loop: // When in check, search starts from here extension = DEPTH_ZERO; captureOrPromotion = pos.capture_or_promotion(move); movedPiece = pos.moved_piece(move); - givesCheck = gives_check(pos, move); + givesCheck = pos.gives_check(move); // Step 13. Extensions (~70 Elo) @@ -940,6 +931,10 @@ moves_loop: // When in check, search starts from here && (pos.blockers_for_king(~us) & from_sq(move) || pos.see_ge(move))) extension = ONE_PLY; + // Shuffle extension + else if(pos.rule50_count() > 14 && ss->ply > 14 && depth < 3 * ONE_PLY && PvNode) + extension = ONE_PLY; + // Castling extension else if (type_of(move) == CASTLING) extension = ONE_PLY; @@ -953,7 +948,7 @@ moves_loop: // When in check, search starts from here && bestValue > VALUE_MATED_IN_MAX_PLY) { // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold - moveCountPruning = moveCount >= futility_move_count(improving,depth / ONE_PLY); + moveCountPruning = moveCount >= futility_move_count(improving, depth / ONE_PLY); if ( !captureOrPromotion && !givesCheck @@ -982,8 +977,7 @@ moves_loop: // When in check, search starts from here if (!pos.see_ge(move, Value(-29 * lmrDepth * lmrDepth))) continue; } - else if ( !extension // (~20 Elo) - && !pos.see_ge(move, -PawnValueEg * (depth / ONE_PLY))) + else if (!pos.see_ge(move, -PawnValueEg * (depth / ONE_PLY))) // (~20 Elo) continue; } @@ -1110,8 +1104,8 @@ moves_loop: // When in check, search starts from here // We record how often the best move has been changed in each // iteration. This information is used for time management: When // the best move changes frequently, we allocate some more time. - if (moveCount > 1 && thisThread == Threads.main()) - ++static_cast(thisThread)->bestMoveChanges; + if (moveCount > 1) + ++thisThread->bestMoveChanges; } else // All other moves but the PV are set to the lowest value: this @@ -1197,7 +1191,7 @@ moves_loop: // When in check, search starts from here tte->save(posKey, value_to_tt(bestValue, ss->ply), ttPv, bestValue >= beta ? BOUND_LOWER : PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, - depth, bestMove, pureStaticEval); + depth, bestMove, ss->staticEval); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1326,7 +1320,7 @@ moves_loop: // When in check, search starts from here { assert(is_ok(move)); - givesCheck = gives_check(pos, move); + givesCheck = pos.gives_check(move); moveCount++;