1
0
Fork 0

Use atomics instead of volatile

Rely on well defined behaviour for message passing, instead of volatile. Two
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
atomics
lucasart 2015-10-24 22:50:51 +01:00 committed by Joona Kiiski
parent 307a5a4f63
commit a4cdc35ea8
4 changed files with 22 additions and 20 deletions

View 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)

View 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;

View 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;

View 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 {