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;
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
// 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<bool>())
{
}
}
+ // 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<int>();
SkillLevel = Options["Skill Level"].value<int>();
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<bool>())
{
- int t = current_search_time();
+ int e = elapsed_search_time();
Log log(Options["Search Log Filename"].value<string>());
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;
// 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);
if (Options["Use Search Log"].value<bool>())
{
Log log(Options["Search Log Filename"].value<string>());
- 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
&& 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
// 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
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;
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<false>()
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;
// 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;
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)
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
}
-// 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")
{
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;
+}
#include <iostream>
+#include "search.h"
#include "thread.h"
#include "ucioption.h"
// 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) {
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);
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);
// 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;
void ThreadsManager::exit() {
- for (int i = 0; i <= MAX_THREADS; i++)
+ for (int i = 0; i < MAX_THREADS + 2; i++)
{
if (i != 0)
{
template Value ThreadsManager::split<true>(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() {
if (cmd == "quit")
is_searching = false;
- Threads.do_uci_async_cmd(cmd);
+ do_uci_async_cmd(cmd);
cmd = ""; // Input has been consumed
}