]> git.sesse.net Git - stockfish/commitdiff
Get rid of timer thread
authorMarco Costalba <mcostalba@gmail.com>
Tue, 3 Nov 2015 10:15:14 +0000 (11:15 +0100)
committerMarco Costalba <mcostalba@gmail.com>
Tue, 3 Nov 2015 10:27:00 +0000 (11:27 +0100)
Unfortunately std::condition_variable::wait_for()
is not accurate in general case and the timer thread
can wake up also after tens or even hundreds of
millisecs after time has elapsded. CPU load, process
priorities, number of concurrent threads, even from
other processes, will have effect upon it.

Even official documentation says: "This function may
block for longer than timeout_duration due to scheduling
or resource contention delays."

So retire timer and use a polling scheme based on a
local thread counter that counts search() calls and
a small trick to keep polling frequency constant,
independently from the number of threads.

Tested for no regression at very fast TC 2+0.05 th 7:
LLR: 2.96 (-2.94,2.94) [-3.00,1.00]
Total: 32969 W: 6720 L: 6620 D: 19629

TC 2+0.05 th 1:
LLR: 2.95 (-2.94,2.94) [-3.00,1.00]
Total: 7765 W: 1917 L: 1765 D: 4083

And at STC TC, both single thread
LLR: 2.96 (-2.94,2.94) [-3.00,1.00]
Total: 15587 W: 3036 L: 2905 D: 9646

And with 7 threads
LLR: 2.95 (-2.94,2.94) [-3.00,1.00]
Total: 8149 W: 1367 L: 1227 D: 5555

bench: 8639247

src/search.cpp
src/search.h
src/thread.cpp
src/thread.h

index 39a731936f4b6e2b59bea6c203a31c643a0f9c62..eb9a0891d697edd3d827556bbd5e1fbcf7ef1a15 100644 (file)
@@ -141,6 +141,7 @@ namespace {
   Value value_from_tt(Value v, int ply);
   void update_pv(Move* pv, Move move, Move* childPv);
   void update_stats(const Position& pos, Stack* ss, Move move, Depth depth, Move* quiets, int quietsCnt);
   Value value_from_tt(Value v, int ply);
   void update_pv(Move* pv, Move move, Move* childPv);
   void update_stats(const Position& pos, Stack* ss, Move move, Depth depth, Move* quiets, int quietsCnt);
+  void check_time();
 
 } // namespace
 
 
 } // namespace
 
@@ -298,14 +299,10 @@ void MainThread::think() {
           }
       }
 
           }
       }
 
-      Threads.timer->run = true;
-      Threads.timer->notify_one(); // Start the recurring timer
-
       search(true); // Let's start searching!
 
       search(true); // Let's start searching!
 
-      // Stop the threads and the timer
+      // Stop the threads
       Signals.stop = true;
       Signals.stop = true;
-      Threads.timer->run = false;
 
       // Wait until all threads have finished
       for (Thread* th : Threads)
 
       // Wait until all threads have finished
       for (Thread* th : Threads)
@@ -585,6 +582,20 @@ namespace {
     bestValue = -VALUE_INFINITE;
     ss->ply = (ss-1)->ply + 1;
 
     bestValue = -VALUE_INFINITE;
     ss->ply = (ss-1)->ply + 1;
 
+    // Check for available remaining time
+    if (thisThread->resetCallsCnt.load(std::memory_order_relaxed))
+    {
+        thisThread->resetCallsCnt = false;
+        thisThread->callsCnt = 0;
+    }
+    if (++thisThread->callsCnt > 4096)
+    {
+        for (Thread* th : Threads)
+            th->resetCallsCnt = true;
+
+        check_time();
+    }
+
     // Used to send selDepth info to GUI
     if (PvNode && thisThread->maxPly < ss->ply)
         thisThread->maxPly = ss->ply;
     // Used to send selDepth info to GUI
     if (PvNode && thisThread->maxPly < ss->ply)
         thisThread->maxPly = ss->ply;
@@ -1455,6 +1466,43 @@ moves_loop: // When in check search starts from here
     return best;
   }
 
     return best;
   }
 
+
+  // check_time() is used to print debug info and, more importantly, to detect
+  // when we are out of available time and thus stop the search.
+
+  void check_time() {
+
+    static TimePoint lastInfoTime = now();
+
+    int elapsed = Time.elapsed();
+    TimePoint tick = Limits.startTime + elapsed;
+
+    if (tick - lastInfoTime >= 1000)
+    {
+        lastInfoTime = tick;
+        dbg_print();
+    }
+
+    // An engine may not stop pondering until told so by the GUI
+    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)
+            Signals.stop = true;
+  }
+
 } // namespace
 
 
 } // namespace
 
 
@@ -1565,40 +1613,3 @@ bool RootMove::extract_ponder_from_tt(Position& pos)
 
     return false;
 }
 
     return false;
 }
-
-
-/// TimerThread::check_time() is called by when the timer triggers. It is used
-/// to print debug info and, more importantly, to detect when we are out of
-/// available time and thus stop the search.
-
-void TimerThread::check_time() {
-
-  static TimePoint lastInfoTime = now();
-  int elapsed = Time.elapsed();
-
-  if (now() - lastInfoTime >= 1000)
-  {
-      lastInfoTime = now();
-      dbg_print();
-  }
-
-  // An engine may not stop pondering until told so by the GUI
-  if (Limits.ponder)
-      return;
-
-  if (Limits.use_time_management())
-  {
-      bool stillAtFirstMove =    Signals.firstRootMove
-                             && !Signals.failedLowAtRoot
-                             &&  elapsed > Time.available() * 3 / 4;
-
-      if (   stillAtFirstMove
-          || elapsed > Time.maximum() - 2 * TimerThread::Resolution)
-          Signals.stop = true;
-  }
-  else if (Limits.movetime && elapsed >= Limits.movetime)
-      Signals.stop = true;
-
-  else if (Limits.nodes && Threads.nodes_searched() >= Limits.nodes)
-          Signals.stop = true;
-}
index 809a15d1cf2ee63735a27337f8ac1150fec274b0..96c0a2d17a476ed42859067d2d29e44b99e7090c 100644 (file)
@@ -93,7 +93,7 @@ struct LimitsType {
 /// typically in an async fashion e.g. to stop the search by the GUI.
 
 struct SignalsType {
 /// 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, firstRootMove, failedLowAtRoot;
 };
 
 typedef std::unique_ptr<std::stack<StateInfo>> StateStackPtr;
 };
 
 typedef std::unique_ptr<std::stack<StateInfo>> StateStackPtr;
index 34424d3791c7b30fdabab421b93c506c93f285a0..c76b4b707e9f97dfe641670de06021b982481287 100644 (file)
@@ -66,7 +66,7 @@ void ThreadBase::notify_one() {
 
 // ThreadBase::wait() set the thread to sleep until 'condition' turns true
 
 
 // ThreadBase::wait() set the thread to sleep until 'condition' turns true
 
-void ThreadBase::wait(std::atomic<bool>& condition) {
+void ThreadBase::wait(std::atomic_bool& condition) {
 
   std::unique_lock<Mutex> lk(mutex);
   sleepCondition.wait(lk, [&]{ return bool(condition); });
 
   std::unique_lock<Mutex> lk(mutex);
   sleepCondition.wait(lk, [&]{ return bool(condition); });
@@ -74,7 +74,7 @@ void ThreadBase::wait(std::atomic<bool>& condition) {
 
 
 // ThreadBase::wait_while() set the thread to sleep until 'condition' turns false
 
 
 // ThreadBase::wait_while() set the thread to sleep until 'condition' turns false
-void ThreadBase::wait_while(std::atomic<bool>& condition) {
+void ThreadBase::wait_while(std::atomic_bool& condition) {
 
   std::unique_lock<Mutex> lk(mutex);
   sleepCondition.wait(lk, [&]{ return !condition; });
 
   std::unique_lock<Mutex> lk(mutex);
   sleepCondition.wait(lk, [&]{ return !condition; });
@@ -86,34 +86,14 @@ void ThreadBase::wait_while(std::atomic<bool>& condition) {
 
 Thread::Thread() {
 
 
 Thread::Thread() {
 
-  searching = false;
-  maxPly = 0;
+  searching = resetCallsCnt = false;
+  maxPly = callsCnt = 0;
   history.clear();
   counterMoves.clear();
   idx = Threads.size(); // Starts from 0
 }
 
 
   history.clear();
   counterMoves.clear();
   idx = Threads.size(); // Starts from 0
 }
 
 
-// TimerThread::idle_loop() is where the timer thread waits Resolution milliseconds
-// and then calls check_time(). When not searching, thread sleeps until it's woken up.
-
-void TimerThread::idle_loop() {
-
-  while (!exit)
-  {
-      std::unique_lock<Mutex> lk(mutex);
-
-      if (!exit)
-          sleepCondition.wait_for(lk, std::chrono::milliseconds(run ? Resolution : INT_MAX));
-
-      lk.unlock();
-
-      if (!exit && run)
-          check_time();
-  }
-}
-
-
 // Thread::idle_loop() is where the thread is parked when it has no work to do
 
 void Thread::idle_loop() {
 // Thread::idle_loop() is where the thread is parked when it has no work to do
 
 void Thread::idle_loop() {
@@ -174,7 +154,6 @@ void MainThread::join() {
 
 void ThreadPool::init() {
 
 
 void ThreadPool::init() {
 
-  timer = new_thread<TimerThread>();
   push_back(new_thread<MainThread>());
   read_uci_options();
 }
   push_back(new_thread<MainThread>());
   read_uci_options();
 }
@@ -185,9 +164,6 @@ void ThreadPool::init() {
 
 void ThreadPool::exit() {
 
 
 void ThreadPool::exit() {
 
-  delete_thread(timer); // As first because check_time() accesses threads data
-  timer = nullptr;
-
   for (Thread* th : *this)
       delete_thread(th);
 
   for (Thread* th : *this)
       delete_thread(th);
 
index fd7343a8f57a1d9d2bc0f1c40f27c94ad1148098..6cceca7244f59b44205105aa26f3595217751ec4 100644 (file)
@@ -44,12 +44,12 @@ struct ThreadBase : public std::thread {
   virtual ~ThreadBase() = default;
   virtual void idle_loop() = 0;
   void notify_one();
   virtual ~ThreadBase() = default;
   virtual void idle_loop() = 0;
   void notify_one();
-  void wait(std::atomic<bool>& b);
-  void wait_while(std::atomic<bool>& b);
+  void wait(std::atomic_bool& b);
+  void wait_while(std::atomic_bool& b);
 
   Mutex mutex;
   ConditionVariable sleepCondition;
 
   Mutex mutex;
   ConditionVariable sleepCondition;
-  std::atomic<bool> exit;
+  std::atomic_bool exit;
 };
 
 
 };
 
 
@@ -68,8 +68,8 @@ struct Thread : public ThreadBase {
   Material::Table materialTable;
   Endgames endgames;
   size_t idx, PVIdx;
   Material::Table materialTable;
   Endgames endgames;
   size_t idx, PVIdx;
-  int maxPly;
-  std::atomic<bool> searching;
+  int maxPly, callsCnt;
+  std::atomic_bool searching, resetCallsCnt;
 
   Position rootPos;
   Search::RootMoveVector rootMoves;
 
   Position rootPos;
   Search::RootMoveVector rootMoves;
@@ -80,25 +80,14 @@ struct Thread : public ThreadBase {
 };
 
 
 };
 
 
-/// MainThread and TimerThread are derived classes used to characterize the two
-/// special threads: the main one and the recurring timer.
+/// MainThread is a derived classes used to characterize the the main one
 
 struct MainThread : public Thread {
   MainThread() { thinking = true; } // Avoid a race with start_thinking()
   virtual void idle_loop();
   void join();
   void think();
 
 struct MainThread : public Thread {
   MainThread() { thinking = true; } // Avoid a race with start_thinking()
   virtual void idle_loop();
   void join();
   void think();
-  std::atomic<bool> thinking;
-};
-
-struct TimerThread : public ThreadBase {
-
-  static const int Resolution = 5; // Millisec between two check_time() calls
-
-  virtual void idle_loop();
-  void check_time();
-
-  bool run = false;
+  std::atomic_bool thinking;
 };
 
 
 };
 
 
@@ -108,14 +97,13 @@ struct TimerThread : public ThreadBase {
 
 struct ThreadPool : public std::vector<Thread*> {
 
 
 struct ThreadPool : public std::vector<Thread*> {
 
-  void init(); // No constructor and destructor, threads rely on globals that should 
+  void init(); // No constructor and destructor, threads rely on globals that should
   void exit(); // be initialized and valid during the whole thread lifetime.
 
   MainThread* main() { return static_cast<MainThread*>(at(0)); }
   void read_uci_options();
   void start_thinking(const Position&, const Search::LimitsType&, Search::StateStackPtr&);
   int64_t nodes_searched();
   void exit(); // be initialized and valid during the whole thread lifetime.
 
   MainThread* main() { return static_cast<MainThread*>(at(0)); }
   void read_uci_options();
   void start_thinking(const Position&, const Search::LimitsType&, Search::StateStackPtr&);
   int64_t nodes_searched();
-  TimerThread* timer;
 };
 
 extern ThreadPool Threads;
 };
 
 extern ThreadPool Threads;