From: Leonid Pechenik Date: Wed, 9 Dec 2015 07:07:34 +0000 (-0500) Subject: Simplify time management and fix 'ponder on' bug X-Git-Url: https://git.sesse.net/?p=stockfish;a=commitdiff_plain;h=69240a982d8c3a2d01fab04c284be43853ab2bc9;ds=sidebyside Simplify time management and fix 'ponder on' bug Simplify time management code by removing hard stops for unchanging first root moves. Search is now stopped earlier at the end iteration if it did not have fail-lows at root. This simplification also fixes pondering bug. Ponder flag was true by default and cutechess-cli doesn't change it to false even though no pondering is possible. Fix the issue by setting the default value of 'Ponder' flag to false. 10+0.1: ELO: 3.51 +-3.0 (95%) LOS: 99.0% Total: 20000 W: 3898 L: 3696 D: 12406 40+0.4: ELO: 1.39 +-2.7 (95%) LOS: 84.7% Total: 20000 W: 3104 L: 3024 D: 13872 60+0.06: LLR: 2.95 (-2.94,2.94) [-3.00,1.00] Total: 37231 W: 5333 L: 5236 D: 26662 Stopped run at 100+1: LLR: 1.09 (-2.94,2.94) [-3.00,1.00] Total: 37253 W: 4862 L: 4856 D: 27535 Resolves #523 Fixes #510 --- diff --git a/src/search.cpp b/src/search.cpp index 3c34f185..e5c485fc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -127,6 +127,7 @@ namespace { }; EasyMoveManager EasyMove; + bool easyPlayed, failedLow; double BestMoveChanges; Value DrawValue[COLOR_NB]; CounterMovesHistoryStats CounterMovesHistory; @@ -368,6 +369,7 @@ void Thread::search() { { easyMove = EasyMove.get(rootPos.key()); EasyMove.clear(); + easyPlayed = false; BestMoveChanges = 0; TT.new_search(); } @@ -391,7 +393,7 @@ void Thread::search() { // Age out PV variability metric if (isMainThread) - BestMoveChanges *= 0.5; + BestMoveChanges *= 0.505, failedLow = false; // 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. @@ -452,7 +454,7 @@ void Thread::search() { if (isMainThread) { - Signals.failedLowAtRoot = true; + failedLow = true; Signals.stopOnPonderhit = false; } } @@ -512,10 +514,10 @@ void Thread::search() { // of the available time has been used or we matched an easyMove // from the previous search and just did a fast verification. if ( rootMoves.size() == 1 - || Time.elapsed() > Time.available() - || ( rootMoves[0].pv[0] == easyMove + || Time.elapsed() > Time.available() * (failedLow? 641 : 315)/640 + || ( easyPlayed = ( rootMoves[0].pv[0] == easyMove && BestMoveChanges < 0.03 - && Time.elapsed() > Time.available() / 10)) + && Time.elapsed() > Time.available() / 8))) { // If we are allowed to ponder do not stop the search now but // keep pondering until the GUI sends "ponderhit" or "stop". @@ -538,7 +540,7 @@ void Thread::search() { // Clear any candidate easy move that wasn't stable for the last search // iterations; the second condition prevents consecutive fast moves. - if (EasyMove.stableCnt < 6 || Time.elapsed() < Time.available()) + if (EasyMove.stableCnt < 6 || easyPlayed) EasyMove.clear(); // If skill level is enabled, swap best PV line with the sub-optimal one @@ -859,15 +861,10 @@ moves_loop: // When in check search starts from here ss->moveCount = ++moveCount; - if (RootNode && thisThread == Threads.main()) - { - Signals.firstRootMove = (moveCount == 1); - - if (Time.elapsed() > 3000) - sync_cout << "info depth " << depth / ONE_PLY - << " currmove " << UCI::move(move, pos.is_chess960()) - << " currmovenumber " << moveCount + thisThread->PVIdx << sync_endl; - } + if (RootNode && thisThread == Threads.main() && Time.elapsed() > 3000) + sync_cout << "info depth " << depth / ONE_PLY + << " currmove " << UCI::move(move, pos.is_chess960()) + << " currmovenumber " << moveCount + thisThread->PVIdx << sync_endl; if (PvNode) (ss+1)->pv = nullptr; @@ -1486,19 +1483,9 @@ moves_loop: // When in check search starts from here if (Limits.ponder) return; - if (Limits.use_time_management()) - { - bool stillAtFirstMove = Signals.firstRootMove.load(std::memory_order_relaxed) - && !Signals.failedLowAtRoot.load(std::memory_order_relaxed) - && elapsed > Time.available() * 3 / 4; - - if (stillAtFirstMove || elapsed > Time.maximum() - 10) - Signals.stop = true; - } - else if (Limits.movetime && elapsed >= Limits.movetime) - Signals.stop = true; - - else if (Limits.nodes && Threads.nodes_searched() >= Limits.nodes) + if ( (Limits.use_time_management() && elapsed > Time.maximum() - 10) + || (Limits.movetime && elapsed >= Limits.movetime) + || (Limits.nodes && Threads.nodes_searched() >= Limits.nodes)) Signals.stop = true; } diff --git a/src/search.h b/src/search.h index d755f1c2..2a9a3737 100644 --- a/src/search.h +++ b/src/search.h @@ -93,7 +93,7 @@ struct LimitsType { /// typically in an async fashion e.g. to stop the search by the GUI. struct SignalsType { - std::atomic_bool stop, stopOnPonderhit, firstRootMove, failedLowAtRoot; + std::atomic_bool stop, stopOnPonderhit; }; typedef std::unique_ptr> StateStackPtr; diff --git a/src/thread.cpp b/src/thread.cpp index c5c8bb0d..3b6074ac 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -174,8 +174,7 @@ void ThreadPool::start_thinking(const Position& pos, const LimitsType& limits, main()->wait_for_search_finished(); - Signals.stopOnPonderhit = Signals.firstRootMove = false; - Signals.stop = Signals.failedLowAtRoot = false; + Signals.stopOnPonderhit = Signals.stop = false; main()->rootMoves.clear(); main()->rootPos = pos; diff --git a/src/timeman.cpp b/src/timeman.cpp index 3a4e157f..47f57ab3 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -32,8 +32,8 @@ namespace { enum TimeType { OptimumTime, MaxTime }; const int MoveHorizon = 50; // Plan time management at most this many moves ahead - const double MaxRatio = 7.0; // When in trouble, we can step over reserved time with this ratio - const double StealRatio = 0.33; // However we must not steal time from remaining moves over this ratio + const double MaxRatio = 6.93; // When in trouble, we can step over reserved time with this ratio + const double StealRatio = 0.36; // However we must not steal time from remaining moves over this ratio // move_importance() is a skew-logistic function based on naive statistical @@ -43,9 +43,9 @@ namespace { double move_importance(int ply) { - const double XScale = 9.3; - const double XShift = 59.8; - const double Skew = 0.172; + const double XScale = 8.27; + const double XShift = 59.; + const double Skew = 0.179; return pow((1 + exp((ply - XShift) / XScale)), -Skew) + DBL_MIN; // Ensure non-zero } @@ -129,6 +129,4 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) if (Options["Ponder"]) optimumTime += optimumTime / 4; - - optimumTime = std::min(optimumTime, maximumTime); } diff --git a/src/timeman.h b/src/timeman.h index b6eb3485..3faba00d 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -31,7 +31,7 @@ class TimeManagement { public: void init(Search::LimitsType& limits, Color us, int ply); void pv_instability(double bestMoveChanges) { unstablePvFactor = 1 + bestMoveChanges; } - int available() const { return int(optimumTime * unstablePvFactor * 0.76); } + int available() const { return int(optimumTime * unstablePvFactor * 1.016); } int maximum() const { return maximumTime; } int elapsed() const { return int(Search::Limits.npmsec ? Threads.nodes_searched() : now() - startTime); } diff --git a/src/ucioption.cpp b/src/ucioption.cpp index f493e840..23af481b 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -61,12 +61,12 @@ void init(OptionsMap& o) { o["Threads"] << Option(1, 1, 128, on_threads); o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); o["Clear Hash"] << Option(on_clear_hash); - o["Ponder"] << Option(true); + o["Ponder"] << Option(false); o["MultiPV"] << Option(1, 1, 500); o["Skill Level"] << Option(20, 0, 20); o["Move Overhead"] << Option(30, 0, 5000); o["Minimum Thinking Time"] << Option(20, 0, 5000); - o["Slow Mover"] << Option(80, 10, 1000); + o["Slow Mover"] << Option(84, 10, 1000); o["nodestime"] << Option(0, 0, 10000); o["UCI_Chess960"] << Option(false); o["SyzygyPath"] << Option("", on_tb_path);