mirror of
https://github.com/sockspls/badfish
synced 2025-04-30 08:43:09 +00:00
Get rid of timer thread
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
This commit is contained in:
parent
27c5cb5912
commit
9c9205860c
4 changed files with 66 additions and 91 deletions
|
@ -141,6 +141,7 @@ namespace {
|
||||||
Value value_from_tt(Value v, int ply);
|
Value value_from_tt(Value v, int ply);
|
||||||
void update_pv(Move* pv, Move move, Move* childPv);
|
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 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
|
// Wait until all threads have finished
|
||||||
for (Thread* th : Threads)
|
for (Thread* th : Threads)
|
||||||
|
@ -585,6 +582,20 @@ namespace {
|
||||||
bestValue = -VALUE_INFINITE;
|
bestValue = -VALUE_INFINITE;
|
||||||
ss->ply = (ss-1)->ply + 1;
|
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
|
// Used to send selDepth info to GUI
|
||||||
if (PvNode && thisThread->maxPly < ss->ply)
|
if (PvNode && thisThread->maxPly < ss->ply)
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -93,7 +93,7 @@ struct LimitsType {
|
||||||
/// typically in an async fashion e.g. to stop the search by the GUI.
|
/// typically in an async fashion e.g. to stop the search by the GUI.
|
||||||
|
|
||||||
struct SignalsType {
|
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;
|
||||||
|
|
|
@ -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);
|
std::unique_lock<Mutex> lk(mutex);
|
||||||
sleepCondition.wait(lk, [&]{ return bool(condition); });
|
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);
|
std::unique_lock<Mutex> lk(mutex);
|
||||||
sleepCondition.wait(lk, [&]{ return !condition; });
|
sleepCondition.wait(lk, [&]{ return !condition; });
|
||||||
|
@ -86,34 +86,14 @@ void ThreadBase::wait_while(std::atomic<bool>& condition) {
|
||||||
|
|
||||||
Thread::Thread() {
|
Thread::Thread() {
|
||||||
|
|
||||||
searching = false;
|
searching = resetCallsCnt = false;
|
||||||
maxPly = 0;
|
maxPly = callsCnt = 0;
|
||||||
history.clear();
|
history.clear();
|
||||||
counterMoves.clear();
|
counterMoves.clear();
|
||||||
idx = Threads.size(); // Starts from 0
|
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
|
// Thread::idle_loop() is where the thread is parked when it has no work to do
|
||||||
|
|
||||||
void Thread::idle_loop() {
|
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>());
|
push_back(new_thread<MainThread>());
|
||||||
read_uci_options();
|
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)
|
for (Thread* th : *this)
|
||||||
delete_thread(th);
|
delete_thread(th);
|
||||||
|
|
||||||
|
|
26
src/thread.h
26
src/thread.h
|
@ -44,12 +44,12 @@ struct ThreadBase : public std::thread {
|
||||||
virtual ~ThreadBase() = default;
|
virtual ~ThreadBase() = default;
|
||||||
virtual void idle_loop() = 0;
|
virtual void idle_loop() = 0;
|
||||||
void notify_one();
|
void notify_one();
|
||||||
void wait(std::atomic<bool>& b);
|
void wait(std::atomic_bool& b);
|
||||||
void wait_while(std::atomic<bool>& b);
|
void wait_while(std::atomic_bool& b);
|
||||||
|
|
||||||
Mutex mutex;
|
Mutex mutex;
|
||||||
ConditionVariable sleepCondition;
|
ConditionVariable sleepCondition;
|
||||||
std::atomic<bool> exit;
|
std::atomic_bool exit;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -68,8 +68,8 @@ struct Thread : public ThreadBase {
|
||||||
Material::Table materialTable;
|
Material::Table materialTable;
|
||||||
Endgames endgames;
|
Endgames endgames;
|
||||||
size_t idx, PVIdx;
|
size_t idx, PVIdx;
|
||||||
int maxPly;
|
int maxPly, callsCnt;
|
||||||
std::atomic<bool> searching;
|
std::atomic_bool searching, resetCallsCnt;
|
||||||
|
|
||||||
Position rootPos;
|
Position rootPos;
|
||||||
Search::RootMoveVector rootMoves;
|
Search::RootMoveVector rootMoves;
|
||||||
|
@ -80,25 +80,14 @@ struct Thread : public ThreadBase {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// MainThread and TimerThread are derived classes used to characterize the two
|
/// MainThread is a derived classes used to characterize the the main one
|
||||||
/// special threads: the main one and the recurring timer.
|
|
||||||
|
|
||||||
struct MainThread : public Thread {
|
struct MainThread : public Thread {
|
||||||
MainThread() { thinking = true; } // Avoid a race with start_thinking()
|
MainThread() { thinking = true; } // Avoid a race with start_thinking()
|
||||||
virtual void idle_loop();
|
virtual void idle_loop();
|
||||||
void join();
|
void join();
|
||||||
void think();
|
void think();
|
||||||
std::atomic<bool> thinking;
|
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -115,7 +104,6 @@ struct ThreadPool : public std::vector<Thread*> {
|
||||||
void read_uci_options();
|
void read_uci_options();
|
||||||
void start_thinking(const Position&, const Search::LimitsType&, Search::StateStackPtr&);
|
void start_thinking(const Position&, const Search::LimitsType&, Search::StateStackPtr&);
|
||||||
int64_t nodes_searched();
|
int64_t nodes_searched();
|
||||||
TimerThread* timer;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern ThreadPool Threads;
|
extern ThreadPool Threads;
|
||||||
|
|
Loading…
Add table
Reference in a new issue