diff --git a/src/lock.h b/src/lock.h index b64293cf..e05db469 100644 --- a/src/lock.h +++ b/src/lock.h @@ -35,6 +35,7 @@ typedef pthread_cond_t WaitCondition; # define cond_init(x) pthread_cond_init(x, NULL) # define cond_signal(x) pthread_cond_signal(x) # define cond_wait(x,y) pthread_cond_wait(x,y) +# define cond_timedwait(x,y,z) pthread_cond_timedwait(x,y,z) #else @@ -57,7 +58,8 @@ typedef CONDITION_VARIABLE WaitCondition; # define cond_destroy(x) (x) # define cond_init(x) InitializeConditionVariable(x) # define cond_signal(x) WakeConditionVariable(x) -# define cond_wait(x,y) SleepConditionVariableSRW(x, y, INFINITE,0) +# define cond_wait(x,y) SleepConditionVariableSRW(x,y,INFINITE,0) +# define cond_timedwait(x,y,z) SleepConditionVariableSRW(x,y,z,0) // Fallback solution to build for Windows XP and older versions, note that // cond_wait() is racy between lock_release() and WaitForSingleObject(). @@ -74,6 +76,8 @@ typedef HANDLE WaitCondition; # define cond_destroy(x) CloseHandle(*x) # define cond_signal(x) SetEvent(*x) # define cond_wait(x,y) { lock_release(y); WaitForSingleObject(*x, INFINITE); lock_grab(y); } +# define cond_timedwait(x,y,z) { lock_release(y); WaitForSingleObject(*x,z); lock_grab(y); } + #endif #endif diff --git a/src/misc.cpp b/src/misc.cpp index 610cc240..5e851f12 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -175,6 +175,39 @@ int cpu_count() { } +/// timed_wait() waits for msec milliseconds. It is mainly an helper to wrap +/// conversion from milliseconds to struct timespec, as used by pthreads. + +#if defined(_MSC_VER) + +void timed_wait(WaitCondition* sleepCond, Lock* sleepLock, int msec) { + cond_timedwait(sleepCond, sleepLock, msec); +} + +#else + +void timed_wait(WaitCondition* sleepCond, Lock* sleepLock, int msec) { + + struct timeval t; + struct timespec abstime; + + gettimeofday(&t, NULL); + + abstime.tv_sec = t.tv_sec + (msec / 1000); + abstime.tv_nsec = (t.tv_usec + (msec % 1000) * 1000) * 1000; + + if (abstime.tv_nsec > 1000000000LL) + { + abstime.tv_sec += 1; + abstime.tv_nsec -= 1000000000LL; + } + + cond_timedwait(sleepCond, sleepLock, &abstime); +} + +#endif + + /// prefetch() preloads the given address in L1/L2 cache. This is a non /// blocking function and do not stalls the CPU waiting for data to be /// loaded from memory, that can be quite slow. diff --git a/src/misc.h b/src/misc.h index 0c89922b..92b97461 100644 --- a/src/misc.h +++ b/src/misc.h @@ -22,12 +22,15 @@ #include #include + +#include "lock.h" #include "types.h" extern const std::string engine_name(); extern const std::string engine_authors(); extern int get_system_time(); extern int cpu_count(); +extern void timed_wait(WaitCondition*, Lock*, int); extern void prefetch(char* addr); extern void dbg_hit_on(bool b); diff --git a/src/search.cpp b/src/search.cpp index 320f480c..fefc8864 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -170,11 +170,6 @@ namespace { int SkillLevel; bool SkillLevelEnabled; - // Node counters, used only by thread[0] but try to keep in different cache - // lines (64 bytes each) from the heavy multi-thread read accessed variables. - int NodesSincePoll; - int NodesBetweenPolls = 30000; - // History table History H; @@ -199,13 +194,12 @@ namespace { void update_history(const Position& pos, Move move, Depth depth, Move movesSearched[], int moveCount); void do_skill_level(Move* best, Move* ponder); - int current_search_time(int set = 0); + int elapsed_search_time(int set = 0); string score_to_uci(Value v, Value alpha = -VALUE_INFINITE, Value beta = VALUE_INFINITE); string speed_to_uci(int64_t nodes); string pv_to_uci(const Move pv[], int pvNum, bool chess960); string pretty_pv(Position& pos, int depth, Value score, int time, Move pv[]); string depth_to_uci(Depth depth); - void poll(const Position& pos); void wait_for_stop_or_ponderhit(); // MovePickerExt template class extends MovePicker and allows to choose at compile @@ -363,24 +357,13 @@ bool think(Position& pos, const SearchLimits& limits, Move searchMoves[]) { // Initialize global search-related variables StopOnPonderhit = StopRequest = QuitRequest = AspirationFailLow = false; - NodesSincePoll = 0; - current_search_time(get_system_time()); + elapsed_search_time(get_system_time()); Limits = limits; TimeMgr.init(Limits, pos.startpos_ply_counter()); // Set output steram in normal or chess960 mode cout << set960(pos.is_chess960()); - // Set best NodesBetweenPolls interval to avoid lagging under time pressure - if (Limits.maxNodes) - NodesBetweenPolls = std::min(Limits.maxNodes, 30000); - else if (Limits.time && Limits.time < 1000) - NodesBetweenPolls = 1000; - else if (Limits.time && Limits.time < 5000) - NodesBetweenPolls = 5000; - else - NodesBetweenPolls = 30000; - // Look for a book move if (Options["OwnBook"].value()) { @@ -398,6 +381,12 @@ bool think(Position& pos, const SearchLimits& limits, Move searchMoves[]) { } } + // Set best timer interval to avoid lagging under time pressure + if (TimeMgr.available_time()) + Threads.set_timer(std::min(100, std::max(TimeMgr.available_time() / 8, 20))); + else + Threads.set_timer(100); + // Read UCI options UCIMultiPV = Options["MultiPV"].value(); SkillLevel = Options["Skill Level"].value(); @@ -447,14 +436,16 @@ bool think(Position& pos, const SearchLimits& limits, Move searchMoves[]) { Move ponderMove = MOVE_NONE; Move bestMove = id_loop(pos, searchMoves, &ponderMove); + Threads.set_timer(0); + // Write final search statistics and close log file if (Options["Use Search Log"].value()) { - int t = current_search_time(); + int e = elapsed_search_time(); Log log(Options["Search Log Filename"].value()); log << "Nodes: " << pos.nodes_searched() - << "\nNodes/second: " << (t > 0 ? pos.nodes_searched() * 1000 / t : 0) + << "\nNodes/second: " << (e > 0 ? pos.nodes_searched() * 1000 / e : 0) << "\nBest move: " << move_to_san(pos, bestMove); StateInfo st; @@ -591,7 +582,7 @@ namespace { // if we have a fail high/low and we are deep in the search. UCI // protocol requires to send all the PV lines also if are still // to be searched and so refer to the previous search's score. - if ((value > alpha && value < beta) || current_search_time() > 2000) + if ((value > alpha && value < beta) || elapsed_search_time() > 2000) for (int i = 0; i < std::min(UCIMultiPV, (int)Rml.size()); i++) { bool updated = (i <= MultiPVIdx); @@ -644,7 +635,7 @@ namespace { if (Options["Use Search Log"].value()) { Log log(Options["Search Log Filename"].value()); - log << pretty_pv(pos, depth, value, current_search_time(), &Rml[0].pv[0]) << endl; + log << pretty_pv(pos, depth, value, elapsed_search_time(), &Rml[0].pv[0]) << endl; } // Init easyMove at first iteration or drop it if differs from the best move @@ -663,9 +654,9 @@ namespace { && easyMove == bestMove && ( Rml.size() == 1 ||( Rml[0].nodes > (pos.nodes_searched() * 85) / 100 - && current_search_time() > TimeMgr.available_time() / 16) + && elapsed_search_time() > TimeMgr.available_time() / 16) ||( Rml[0].nodes > (pos.nodes_searched() * 98) / 100 - && current_search_time() > TimeMgr.available_time() / 32))) + && elapsed_search_time() > TimeMgr.available_time() / 32))) StopRequest = true; // Take in account some extra time if the best move has changed @@ -674,7 +665,7 @@ namespace { // Stop search if most of available time is already consumed. We probably don't // have enough time to search the first move at the next iteration anyway. - if (current_search_time() > (TimeMgr.available_time() * 62) / 100) + if (elapsed_search_time() > (TimeMgr.available_time() * 62) / 100) StopRequest = true; // If we are allowed to ponder do not stop the search now but keep pondering @@ -743,7 +734,7 @@ namespace { if (PvNode && thread.maxPly < ss->ply) thread.maxPly = ss->ply; - // Step 1. Initialize node and poll. Polling can abort search + // Step 1. Initialize node if (!SpNode) { ss->currentMove = ss->bestMove = threatMove = (ss+1)->excludedMove = MOVE_NONE; @@ -759,12 +750,6 @@ namespace { goto split_point_start; } - if (pos.thread() == 0 && ++NodesSincePoll > NodesBetweenPolls) - { - NodesSincePoll = 0; - poll(pos); - } - // Step 2. Check for aborted search and immediate draw if (( StopRequest || pos.is_draw() @@ -1031,7 +1016,7 @@ split_point_start: // At split points actual search starts from here nodes = pos.nodes_searched(); // For long searches send current move info to GUI - if (pos.thread() == 0 && current_search_time() > 2000) + if (pos.thread() == 0 && elapsed_search_time() > 2000) cout << "info" << depth_to_uci(depth) << " currmove " << move << " currmovenumber " << moveCount + MultiPVIdx << endl; @@ -1745,7 +1730,7 @@ split_point_start: // At split points actual search starts from here // current_search_time() returns the number of milliseconds which have passed // since the beginning of the current search. - int current_search_time(int set) { + int elapsed_search_time(int set) { static int searchStartTime; @@ -1784,7 +1769,7 @@ split_point_start: // At split points actual search starts from here string speed_to_uci(int64_t nodes) { std::stringstream s; - int t = current_search_time(); + int t = elapsed_search_time(); s << " nodes " << nodes << " nps " << (t > 0 ? int(nodes * 1000 / t) : 0) @@ -1910,49 +1895,6 @@ split_point_start: // At split points actual search starts from here return s.str(); } - // poll() performs two different functions: It polls for user input, and it - // looks at the time consumed so far and decides if it's time to abort the - // search. - - void poll(const Position& pos) { - - static int lastInfoTime; - int t = current_search_time(); - - // Print search information - if (t < 1000) - lastInfoTime = 0; - - else if (lastInfoTime > t) - // HACK: Must be a new search where we searched less than - // NodesBetweenPolls nodes during the first second of search. - lastInfoTime = 0; - - else if (t - lastInfoTime >= 1000) - { - lastInfoTime = t; - - dbg_print_mean(); - dbg_print_hit_rate(); - } - - // Should we stop the search? - if (Limits.ponder) - return; - - bool stillAtFirstMove = FirstRootMove - && !AspirationFailLow - && t > TimeMgr.available_time(); - - bool noMoreTime = t > TimeMgr.maximum_time() - || stillAtFirstMove; - - if ( (Limits.useTimeManagement() && noMoreTime) - || (Limits.maxTime && t >= Limits.maxTime) - || (Limits.maxNodes && pos.nodes_searched() >= Limits.maxNodes)) // FIXME - StopRequest = true; - } - // wait_for_stop_or_ponderhit() is called when the maximum depth is reached // while the program is pondering. The point is to work around a wrinkle in @@ -2225,10 +2167,10 @@ void Thread::idle_loop(SplitPoint* sp) { } -// ThreadsManager::do_uci_async_cmd() processes the commands from GUI received -// by listener thread while the other threads are searching. +// do_uci_async_cmd() is called by listener thread when in async mode and 'cmd' +// input line is received from the GUI. -void ThreadsManager::do_uci_async_cmd(const std::string& cmd) { +void do_uci_async_cmd(const std::string& cmd) { if (cmd == "quit") { @@ -2254,3 +2196,37 @@ void ThreadsManager::do_uci_async_cmd(const std::string& cmd) { StopRequest = true; } } + + +// do_timer_event() is called by the timer thread when the timer triggers + +void do_timer_event() { + + static int lastInfoTime; + int e = elapsed_search_time(); + + // Print debug information every second + if (get_system_time() - lastInfoTime >= 1000) + { + lastInfoTime = get_system_time(); + + dbg_print_mean(); + dbg_print_hit_rate(); + } + + // Should we stop the search? + if (Limits.ponder) + return; + + bool stillAtFirstMove = FirstRootMove + && !AspirationFailLow + && e > TimeMgr.available_time(); + + bool noMoreTime = e > TimeMgr.maximum_time() + || stillAtFirstMove; + + if ( (Limits.useTimeManagement() && noMoreTime) + || (Limits.maxTime && e >= Limits.maxTime) + /* missing nodes limit */ ) // FIXME + StopRequest = true; +} diff --git a/src/search.h b/src/search.h index 1688534b..757aeb00 100644 --- a/src/search.h +++ b/src/search.h @@ -67,5 +67,7 @@ struct SearchLimits { extern void init_search(); extern int64_t perft(Position& pos, Depth depth); extern bool think(Position& pos, const SearchLimits& limits, Move searchMoves[]); +extern void do_uci_async_cmd(const std::string& cmd); +extern void do_timer_event(); #endif // !defined(SEARCH_H_INCLUDED) diff --git a/src/thread.cpp b/src/thread.cpp index 49a598f1..df76dcca 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -19,6 +19,7 @@ #include +#include "search.h" #include "thread.h" #include "ucioption.h" @@ -28,7 +29,8 @@ namespace { extern "C" { // start_routine() is the C function which is called when a new thread // is launched. It simply calls idle_loop() of the supplied thread. The - // last thread is dedicated to I/O and so runs in listener_loop(). + // last two threads are dedicated to read input from GUI and to mimic a + // timer, so they run in listener_loop() and timer_loop() respectively. #if defined(_MSC_VER) DWORD WINAPI start_routine(LPVOID thread) { @@ -38,6 +40,9 @@ namespace { extern "C" { if (((Thread*)thread)->threadID == MAX_THREADS) ((Thread*)thread)->listener_loop(); + + else if (((Thread*)thread)->threadID == MAX_THREADS + 1) + ((Thread*)thread)->timer_loop(); else ((Thread*)thread)->idle_loop(NULL); @@ -149,7 +154,7 @@ void ThreadsManager::init() { lock_init(&threadsLock); // Initialize sleep and split point locks - for (int i = 0; i <= MAX_THREADS; i++) + for (int i = 0; i < MAX_THREADS + 2; i++) { lock_init(&threads[i].sleepLock); cond_init(&threads[i].sleepCond); @@ -165,7 +170,7 @@ void ThreadsManager::init() { // Create and launch all the threads but the main that is already running, // threads will go immediately to sleep. - for (int i = 1; i <= MAX_THREADS; i++) + for (int i = 1; i < MAX_THREADS + 2; i++) { threads[i].is_searching = false; threads[i].threadID = i; @@ -190,7 +195,7 @@ void ThreadsManager::init() { void ThreadsManager::exit() { - for (int i = 0; i <= MAX_THREADS; i++) + for (int i = 0; i < MAX_THREADS + 2; i++) { if (i != 0) { @@ -349,10 +354,39 @@ template Value ThreadsManager::split(Position&, SearchStack*, Value, Valu template Value ThreadsManager::split(Position&, SearchStack*, Value, Value, Value, Depth, Move, int, MovePicker*, int); -// Thread::listner_loop() is where the last thread, used for IO, waits for input. -// Input is read in sync with main thread (that blocks) when is_searching is set -// to false, otherwise IO thread reads any input asynchronously and processes -// the input line calling do_uci_async_cmd(). +// Thread::timer_loop() is where the timer thread waits maxPly milliseconds +// and then calls do_timer_event(). + +void Thread::timer_loop() { + + while (!do_terminate) + { + lock_grab(&sleepLock); + timed_wait(&sleepCond, &sleepLock, maxPly ? maxPly : INT_MAX); + lock_release(&sleepLock); + do_timer_event(); + } +} + + +// ThreadsManager::set_timer() is used to set the timer to trigger after msec +// milliseconds. If msec is 0 then timer is stopped. + +void ThreadsManager::set_timer(int msec) { + + Thread& timer = threads[MAX_THREADS + 1]; + + lock_grab(&timer.sleepLock); + timer.maxPly = msec; + cond_signal(&timer.sleepCond); // Wake up and restart the timer + lock_release(&timer.sleepLock); +} + + +// Thread::listener_loop() is where the listener thread, used for I/O, waits for +// input. When is_searching is false then input is read in sync with main thread +// (that blocks), otherwise the listener thread reads any input asynchronously +// and processes the input line calling do_uci_async_cmd(). void Thread::listener_loop() { @@ -390,7 +424,7 @@ void Thread::listener_loop() { if (cmd == "quit") is_searching = false; - Threads.do_uci_async_cmd(cmd); + do_uci_async_cmd(cmd); cmd = ""; // Input has been consumed } diff --git a/src/thread.h b/src/thread.h index 124815b4..46ce03aa 100644 --- a/src/thread.h +++ b/src/thread.h @@ -70,6 +70,7 @@ struct Thread { bool is_available_to(int master) const; void idle_loop(SplitPoint* sp); void listener_loop(); + void timer_loop(); SplitPoint splitPoints[MAX_ACTIVE_SPLIT_POINTS]; MaterialInfoTable materialTable; @@ -115,9 +116,9 @@ public: bool available_slave_exists(int master) const; void getline(std::string& cmd); - void do_uci_async_cmd(const std::string& cmd); void start_listener(); void stop_listener(); + void set_timer(int msec); template Value split(Position& pos, SearchStack* ss, Value alpha, Value beta, Value bestValue, @@ -125,7 +126,7 @@ public: private: friend struct Thread; - Thread threads[MAX_THREADS + 1]; + Thread threads[MAX_THREADS + 2]; // Last 2 are the listener and the timer Lock threadsLock; Depth minimumSplitDepth; int maxThreadsPerSplitPoint; diff --git a/src/timeman.cpp b/src/timeman.cpp index b6916900..edbc34e4 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -79,8 +79,8 @@ namespace { void TimeManager::pv_instability(int curChanges, int prevChanges) { - unstablePVExtraTime = curChanges * (optimumSearchTime / 2) - + prevChanges * (optimumSearchTime / 3); + unstablePVExtraTime = curChanges * (optimumSearchTime / 2) + + prevChanges * (optimumSearchTime / 3); }