Use atomics instead of volatile
authorlucasart <lucas.braesch@gmail.com>
Sat, 24 Oct 2015 21:50:51 +0000 (22:50 +0100)
committerJoona Kiiski <joona@zoox.com>
Sun, 25 Oct 2015 09:15:45 +0000 (09:15 +0000)
Rely on well defined behaviour for message passing, instead of volatile. Three
versions have been tested, to make sure this wouldn't cause a slowdown on any
platform.

v1: Sequentially consistent atomics

No mesurable regression, despite the extra memory barriers on x86. Even with 15
threads and extreme time pressure, both acting as a magnifying glass:

threads=15, tc=2+0.02
ELO: 2.59 +-3.4 (95%) LOS: 93.3%
Total: 18132 W: 4113 L: 3978 D: 10041

threads=7, tc=2+0.02
ELO: -1.64 +-3.6 (95%) LOS: 18.8%
Total: 16914 W: 4053 L: 4133 D: 8728

v2: Acquire/Release semantics

This version generates no extra barriers for x86 (on the hot path). As expected,
no regression either, under the same conditions:

threads=15, tc=2+0.02
ELO: 2.85 +-3.3 (95%) LOS: 95.4%
Total: 19661 W: 4640 L: 4479 D: 10542

threads=7, tc=2+0.02
ELO: 0.23 +-3.5 (95%) LOS: 55.1%
Total: 18108 W: 4326 L: 4314 D: 9468

As suggested by Joona, another test at LTC:

threads=15, tc=20+0.05
ELO: 0.64 +-2.6 (95%) LOS: 68.3%
Total: 20000 W: 3053 L: 3016 D: 13931

v3: Final version: SeqCst/Relaxed

threads=15, tc=10+0.1
ELO: 0.87 +-3.9 (95%) LOS: 67.1%
Total: 9541 W: 1478 L: 1454 D: 6609

Resolves #474

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

index 78e4748..902ba0f 100644 (file)
@@ -37,7 +37,7 @@
 
 namespace Search {
 
-  volatile SignalsType Signals;
+  SignalsType Signals;
   LimitsType Limits;
   StateStackPtr SetupStates;
 }
@@ -581,8 +581,8 @@ namespace {
     if (!RootNode)
     {
         // Step 2. Check for aborted search and immediate draw
-        if (Signals.stop || pos.is_draw() || ss->ply >= MAX_PLY)
-            return ss->ply >= MAX_PLY && !inCheck ? evaluate(pos)
+        if (Signals.stop.load(std::memory_order_relaxed) || pos.is_draw() || ss->ply >= MAX_PLY)
+            return ss->ply >= MAX_PLY && !inCheck ? evaluate(pos) 
                                                   : DrawValue[pos.side_to_move()];
 
         // Step 3. Mate distance pruning. Even if we mate at the next move our score
@@ -841,7 +841,7 @@ moves_loop: // When in check search starts from here
 
       if (RootNode && thisThread == Threads.main())
       {
-          Signals.firstRootMove = (moveCount == 1);
+          Signals.firstRootMove = moveCount == 1;
 
           if (Time.elapsed() > 3000)
               sync_cout << "info depth " << depth / ONE_PLY
@@ -1008,7 +1008,7 @@ moves_loop: // When in check search starts from here
       // Finished searching the move. If a stop occurred, the return value of
       // the search cannot be trusted, and we return immediately without
       // updating best move, PV and TT.
-      if (Signals.stop)
+      if (Signals.stop.load(std::memory_order_relaxed))
           return VALUE_ZERO;
 
       if (RootNode)
@@ -1577,7 +1577,7 @@ void check_time() {
   {
       bool stillAtFirstMove =    Signals.firstRootMove
                              && !Signals.failedLowAtRoot
-                             &&  elapsed > Time.available() * 75 / 100;
+                             &&  elapsed > Time.available() * 3 / 4;
 
       if (   stillAtFirstMove
           || elapsed > Time.maximum() - 2 * TimerThread::Resolution)
index c7abb9d..740063b 100644 (file)
@@ -20,7 +20,8 @@
 #ifndef SEARCH_H_INCLUDED
 #define SEARCH_H_INCLUDED
 
-#include <memory>  // For std::auto_ptr
+#include <atomic>
+#include <memory>  // For std::unique_ptr
 #include <stack>
 #include <vector>
 
@@ -91,16 +92,16 @@ struct LimitsType {
   TimePoint startTime;
 };
 
-/// The SignalsType struct stores volatile flags updated during the search
+/// The SignalsType struct stores atomic flags updated during the search
 /// typically in an async fashion e.g. to stop the search by the GUI.
 
 struct SignalsType {
-  bool stop, stopOnPonderhit, firstRootMove, failedLowAtRoot;
+  std::atomic<bool> stop, stopOnPonderhit, firstRootMove, failedLowAtRoot;
 };
 
 typedef std::unique_ptr<std::stack<StateInfo>> StateStackPtr;
 
-extern volatile SignalsType Signals;
+extern SignalsType Signals;
 extern LimitsType Limits;
 extern StateStackPtr SetupStates;
 
index dd8c398..eb64f7e 100644 (file)
@@ -68,16 +68,15 @@ void ThreadBase::notify_one() {
 
 // ThreadBase::wait() set the thread to sleep until 'condition' turns true
 
-void ThreadBase::wait(volatile const bool& condition) {
+void ThreadBase::wait(std::atomic<bool>& condition) {
 
   std::unique_lock<Mutex> lk(mutex);
-  sleepCondition.wait(lk, [&]{ return condition; });
+  sleepCondition.wait(lk, [&]{ return bool(condition); });
 }
 
 
 // ThreadBase::wait_while() set the thread to sleep until 'condition' turns false
-
-void ThreadBase::wait_while(volatile const bool& condition) {
+void ThreadBase::wait_while(std::atomic<bool>& condition) {
 
   std::unique_lock<Mutex> lk(mutex);
   sleepCondition.wait(lk, [&]{ return !condition; });
@@ -87,7 +86,7 @@ void ThreadBase::wait_while(volatile const bool& condition) {
 // Thread c'tor makes some init but does not launch any execution thread that
 // will be started only when c'tor returns.
 
-Thread::Thread() /* : splitPoints() */ { // Initialization of non POD broken in MSVC
+Thread::Thread() {
 
   searching = false;
   maxPly = 0;
index 459b1dd..abb7a22 100644 (file)
@@ -44,15 +44,16 @@ const size_t MAX_THREADS = 128;
 
 struct ThreadBase : public std::thread {
 
+  ThreadBase() { exit = false; }
   virtual ~ThreadBase() = default;
   virtual void idle_loop() = 0;
   void notify_one();
-  void wait(volatile const bool& b);
-  void wait_while(volatile const bool& b);
+  void wait(std::atomic<bool>& b);
+  void wait_while(std::atomic<bool>& b);
 
   Mutex mutex;
   ConditionVariable sleepCondition;
-  volatile bool exit = false;
+  std::atomic<bool> exit;
 };
 
 
@@ -72,7 +73,7 @@ struct Thread : public ThreadBase {
   Endgames endgames;
   size_t idx, PVIdx;
   int maxPly;
-  volatile bool searching;
+  std::atomic<bool> searching;
 
   Position rootPos;
   Search::RootMoveVector rootMoves;
@@ -87,10 +88,11 @@ struct Thread : public ThreadBase {
 /// special threads: the main one and the recurring timer.
 
 struct MainThread : public Thread {
+  MainThread() { thinking = true; } // Avoid a race with start_thinking()
   virtual void idle_loop();
   void join();
   void think();
-  volatile bool thinking = true; // Avoid a race with start_thinking()
+  std::atomic<bool> thinking;
 };
 
 struct TimerThread : public ThreadBase {