diff --git a/src/search.cpp b/src/search.cpp index 767ea238..684b760e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -190,8 +190,8 @@ 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(limits.inc[rootPos.side_to_move()] - - threads.nodes_searched()); + main_manager()->tm.advance_nodes_time(threads.nodes_searched() + - limits.inc[rootPos.side_to_move()]); Worker* bestThread = this; Skill skill = @@ -347,7 +347,7 @@ void Search::Worker::iterative_deepening() { // When failing high/low give some update (without cluttering // the UI) before a re-search. if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta) - && elapsed() > 3000) + && elapsed_time() > 3000) main_manager()->pv(*this, threads, tt, rootDepth); // In case of failing low/high increase aspiration window and @@ -378,7 +378,7 @@ void Search::Worker::iterative_deepening() { std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1); if (mainThread - && (threads.stop || pvIdx + 1 == multiPV || elapsed() > 3000) + && (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 // had time to fully search other root-moves. Thus we suppress this output and @@ -935,7 +935,7 @@ moves_loop: // When in check, search starts here ss->moveCount = ++moveCount; - if (rootNode && is_mainthread() && elapsed() > 3000) + if (rootNode && is_mainthread() && elapsed_time() > 3000) { main_manager()->updates.onIter( {depth, UCIEngine::move(move, pos.is_chess960()), moveCount + thisThread->pvIdx}); @@ -1647,10 +1647,20 @@ Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) { return (reductionScale + 1318 - delta * 760 / rootDelta) / 1024 + (!i && reductionScale > 1066); } +// elapsed() returns the time elapsed since the search started. If the +// 'nodestime' option is enabled, it will return the count of nodes searched +// instead. This function is called to check whether the search should be +// stopped based on predefined thresholds like time limits or nodes searched. +// +// elapsed_time() returns the actual time elapsed since the start of the search. +// 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 threads.nodes_searched(); }); } +TimePoint Search::Worker::elapsed_time() const { return main_manager()->tm.elapsed_time(); } + namespace { // Adjusts a mate or TB score from "plies to mate from the root" @@ -1900,7 +1910,7 @@ void SearchManager::pv(const Search::Worker& worker, const auto& rootMoves = worker.rootMoves; const auto& pos = worker.rootPos; size_t pvIdx = worker.pvIdx; - TimePoint time = tm.elapsed([nodes]() { return nodes; }) + 1; + TimePoint time = tm.elapsed_time() + 1; size_t multiPV = std::min(size_t(worker.options["MultiPV"]), rootMoves.size()); uint64_t tbHits = threads.tb_hits() + (worker.tbConfig.rootInTB ? rootMoves.size() : 0); diff --git a/src/search.h b/src/search.h index cb73a5af..c824daf9 100644 --- a/src/search.h +++ b/src/search.h @@ -276,6 +276,7 @@ class Worker { } TimePoint elapsed() const; + TimePoint elapsed_time() const; LimitsType limits; diff --git a/src/timeman.cpp b/src/timeman.cpp index c651745f..4feb329b 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -32,12 +32,12 @@ TimePoint TimeManagement::optimum() const { return optimumTime; } TimePoint TimeManagement::maximum() const { return maximumTime; } void TimeManagement::clear() { - availableNodes = 0; // When in 'nodes as time' mode + availableNodes = -1; // When in 'nodes as time' mode } void TimeManagement::advance_nodes_time(std::int64_t nodes) { assert(useNodesTime); - availableNodes += nodes; + availableNodes = std::max(int64_t(0), availableNodes - nodes); } // Called at the beginning of the search and calculates @@ -48,14 +48,17 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply, const OptionsMap& options) { - // If we have no time, no need to initialize TM, except for the start time, - // which is used by movetime. - startTime = limits.startTime; + TimePoint npmsec = TimePoint(options["nodestime"]); + + // If we have no time, we don't need to fully initialize TM. + // startTime is used by movetime and useNodesTime is used in elapsed calls. + startTime = limits.startTime; + useNodesTime = npmsec != 0; + if (limits.time[us] == 0) return; TimePoint moveOverhead = TimePoint(options["Move Overhead"]); - TimePoint npmsec = TimePoint(options["nodestime"]); // optScale is a percentage of available time to use for the current move. // maxScale is a multiplier applied to optimumTime. @@ -65,26 +68,31 @@ void TimeManagement::init(Search::LimitsType& limits, // to nodes, and use resulting values in time management formulas. // WARNING: to avoid time losses, the given npmsec (nodes per millisecond) // must be much lower than the real engine speed. - if (npmsec) + if (useNodesTime) { - useNodesTime = true; - - if (!availableNodes) // Only once at game start + if (availableNodes == -1) // Only once at game start availableNodes = npmsec * limits.time[us]; // Time is in msec // Convert from milliseconds to nodes limits.time[us] = TimePoint(availableNodes); limits.inc[us] *= npmsec; limits.npmsec = npmsec; + moveOverhead *= npmsec; } + // These numbers are used where multiplications, divisions or comparisons + // with constants are involved. + const int64_t scaleFactor = useNodesTime ? npmsec : 1; + const TimePoint scaledTime = limits.time[us] / scaleFactor; + const TimePoint scaledInc = limits.inc[us] / scaleFactor; + // Maximum move horizon of 50 moves int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50; - // if less than one second, gradually reduce mtg - if (limits.time[us] < 1000 && (double(mtg) / limits.time[us] > 0.05)) + // If less than one second, gradually reduce mtg + if (scaledTime < 1000 && double(mtg) / scaledInc > 0.05) { - mtg = limits.time[us] * 0.05; + mtg = scaledTime * 0.05; } // Make sure timeLeft is > 0 since we may use it as a divisor @@ -97,15 +105,15 @@ void TimeManagement::init(Search::LimitsType& limits, if (limits.movestogo == 0) { // Use extra time with larger increments - double optExtra = limits.inc[us] < 500 ? 1.0 : 1.13; + double optExtra = scaledInc < 500 ? 1.0 : 1.13; // Calculate time constants based on current time left. - double optConstant = - std::min(0.00308 + 0.000319 * std::log10(limits.time[us] / 1000.0), 0.00506); - double maxConstant = std::max(3.39 + 3.01 * std::log10(limits.time[us] / 1000.0), 2.93); + double logTimeInSec = std::log10(scaledTime / 1000.0); + double optConstant = std::min(0.00308 + 0.000319 * logTimeInSec, 0.00506); + double maxConstant = std::max(3.39 + 3.01 * logTimeInSec, 2.93); optScale = std::min(0.0122 + std::pow(ply + 2.95, 0.462) * optConstant, - 0.213 * limits.time[us] / double(timeLeft)) + 0.213 * limits.time[us] / timeLeft) * optExtra; maxScale = std::min(6.64, maxConstant + ply / 12.0); } @@ -113,7 +121,7 @@ void TimeManagement::init(Search::LimitsType& limits, // x moves in y seconds (+ z increment) else { - optScale = std::min((0.88 + ply / 116.4) / mtg, 0.88 * limits.time[us] / double(timeLeft)); + optScale = std::min((0.88 + ply / 116.4) / mtg, 0.88 * limits.time[us] / timeLeft); maxScale = std::min(6.3, 1.5 + 0.11 * mtg); } diff --git a/src/timeman.h b/src/timeman.h index 35c3cfc0..1b6bd849 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -42,8 +42,9 @@ class TimeManagement { TimePoint maximum() const; template TimePoint elapsed(FUNC nodes) const { - return useNodesTime ? TimePoint(nodes()) : now() - startTime; + return useNodesTime ? TimePoint(nodes()) : elapsed_time(); } + TimePoint elapsed_time() const { return now() - startTime; }; void clear(); void advance_nodes_time(std::int64_t nodes); @@ -53,7 +54,7 @@ class TimeManagement { TimePoint optimumTime; TimePoint maximumTime; - std::int64_t availableNodes = 0; // When in 'nodes as time' mode + std::int64_t availableNodes = -1; // When in 'nodes as time' mode bool useNodesTime = false; // True if we are in 'nodes as time' mode };