diff --git a/src/Makefile b/src/Makefile index 1e765faa..86afd58a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -389,7 +389,7 @@ profile-build: $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make) @echo "" @echo "Step 2/4. Running benchmark for pgo-build ..." - @sleep 10 | $(PGOBENCH) > /dev/null + @$(PGOBENCH) > /dev/null @echo "" @echo "Step 3/4. Building final executable ..." @touch *.cpp @@ -409,14 +409,14 @@ double-profile-build: $(MAKE) ARCH=x86-64 COMP=$(COMP) $(profile_make) @echo "" @echo "Step 2/6. Running benchmark for pgo-build (popcnt disabled)..." - @sleep 10 | $(PGOBENCH) > /dev/null + @$(PGOBENCH) > /dev/null @echo "" @echo "Step 3/6. Building executable for benchmark (popcnt enabled)..." @touch *.cpp *.h $(MAKE) ARCH=x86-64-modern COMP=$(COMP) $(profile_make) @echo "" @echo "Step 4/6. Running benchmark for pgo-build (popcnt enabled)..." - @sleep 10 | $(PGOBENCH) > /dev/null + @$(PGOBENCH) > /dev/null @echo "" @echo "Step 5/6. Building final executable ..." @touch *.cpp *.h diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 95dd88ac..551c341b 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -23,6 +23,7 @@ #include "position.h" #include "search.h" +#include "thread.h" #include "ucioption.h" using namespace std; @@ -59,7 +60,6 @@ static const string Defaults[] = { void benchmark(int argc, char* argv[]) { vector fenList; - SearchLimits limits; int64_t totalNodes; int time; @@ -76,11 +76,11 @@ void benchmark(int argc, char* argv[]) { // Search should be limited by nodes, time or depth ? if (valType == "nodes") - limits.maxNodes = atoi(valStr.c_str()); + Limits.maxNodes = atoi(valStr.c_str()); else if (valType == "time") - limits.maxTime = 1000 * atoi(valStr.c_str()); // maxTime is in ms + Limits.maxTime = 1000 * atoi(valStr.c_str()); // maxTime is in ms else - limits.maxDepth = atoi(valStr.c_str()); + Limits.maxDepth = atoi(valStr.c_str()); // Do we need to load positions from a given FEN file? if (fenFile != "default") @@ -107,28 +107,27 @@ void benchmark(int argc, char* argv[]) { // Ok, let's start the benchmark ! totalNodes = 0; time = get_system_time(); + SearchMoves.push_back(MOVE_NONE); for (size_t i = 0; i < fenList.size(); i++) { - Move moves[] = { MOVE_NONE }; Position pos(fenList[i], false, 0); + RootPosition = &pos; cerr << "\nBench position: " << i + 1 << '/' << fenList.size() << endl; if (valType == "perft") { - int64_t cnt = perft(pos, limits.maxDepth * ONE_PLY); + int64_t cnt = perft(pos, Limits.maxDepth * ONE_PLY); - cerr << "\nPerft " << limits.maxDepth + cerr << "\nPerft " << Limits.maxDepth << " nodes counted: " << cnt << endl; totalNodes += cnt; } else { - if (!think(pos, limits, moves)) - break; - + Threads.start_thinking(false); totalNodes += pos.nodes_searched(); } } diff --git a/src/search.cpp b/src/search.cpp index 402433ac..560f64f1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -43,6 +43,10 @@ using std::cout; using std::endl; using std::string; +SearchLimits Limits; +std::vector SearchMoves; +Position* RootPosition; + namespace { // Set to true to force running with one thread. Used for debugging @@ -162,9 +166,8 @@ namespace { int MultiPV, UCIMultiPV, MultiPVIdx; // Time management variables - volatile bool StopOnPonderhit, FirstRootMove, StopRequest, QuitRequest, AspirationFailLow; + volatile bool StopOnPonderhit, FirstRootMove, StopRequest, AspirationFailLow; TimeManager TimeMgr; - SearchLimits Limits; // Skill level adjustment int SkillLevel; @@ -200,7 +203,6 @@ namespace { 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 wait_for_stop_or_ponderhit(); // MovePickerExt template class extends MovePicker and allows to choose at compile // time the proper moves source according to the type of node. In the default case @@ -351,16 +353,17 @@ int64_t perft(Position& pos, Depth depth) { /// variables, and calls id_loop(). It returns false when a "quit" command is /// received during the search. -bool think(Position& pos, const SearchLimits& limits, Move searchMoves[]) { +void think() { static Book book; // Defined static to initialize the PRNG only once + Position& pos = *RootPosition; + // Save "search start" time and reset elapsed time to zero elapsed_search_time(get_system_time()); // Initialize global search-related variables - StopOnPonderhit = StopRequest = QuitRequest = AspirationFailLow = false; - Limits = limits; + StopOnPonderhit = StopRequest = AspirationFailLow = false; // Set output stream mode: normal or chess960. Castling notation is different cout << set960(pos.is_chess960()); @@ -374,11 +377,11 @@ bool think(Position& pos, const SearchLimits& limits, Move searchMoves[]) { Move bookMove = book.probe(pos, Options["Best Book Move"].value()); if (bookMove != MOVE_NONE) { - if (Limits.ponder) - wait_for_stop_or_ponderhit(); + if (!StopRequest && (Limits.ponder || Limits.infinite)) + Threads.wait_for_stop_or_ponderhit(); cout << "bestmove " << bookMove << endl; - return !QuitRequest; + return; } } @@ -432,16 +435,9 @@ bool think(Position& pos, const SearchLimits& limits, Move searchMoves[]) { else Threads.set_timer(100); - // Start async mode to catch UCI commands sent to us while searching, - // like "quit", "stop", etc. - Threads.start_listener(); - // We're ready to start thinking. Call the iterative deepening loop function Move ponderMove = MOVE_NONE; - Move bestMove = id_loop(pos, searchMoves, &ponderMove); - - // From now on any UCI command will be read in-sync with Threads.getline() - Threads.stop_listener(); + Move bestMove = id_loop(pos, &SearchMoves[0], &ponderMove); // Stop timer, no need to check for available time any more Threads.set_timer(0); @@ -469,7 +465,7 @@ bool think(Position& pos, const SearchLimits& limits, Move searchMoves[]) { // we are pondering or in infinite search, we shouldn't print the best move // before we are told to do so. if (!StopRequest && (Limits.ponder || Limits.infinite)) - wait_for_stop_or_ponderhit(); + Threads.wait_for_stop_or_ponderhit(); // Could be MOVE_NONE when searching on a stalemate position cout << "bestmove " << bestMove; @@ -480,8 +476,6 @@ bool think(Position& pos, const SearchLimits& limits, Move searchMoves[]) { cout << " ponder " << ponderMove; cout << endl; - - return !QuitRequest; } @@ -1902,26 +1896,6 @@ split_point_start: // At split points actual search starts from here } - // 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 - // the UCI protocol: When pondering, the engine is not allowed to give a - // "bestmove" before the GUI sends it a "stop" or "ponderhit" command. - // We simply wait here until one of these commands (that raise StopRequest) is - // sent, and return, after which the bestmove and pondermove will be printed. - - void wait_for_stop_or_ponderhit() { - - string cmd; - StopOnPonderhit = true; - - while (!StopRequest) - { - Threads.getline(cmd); - do_uci_async_cmd(cmd); - } - } - - // When playing with strength handicap choose best move among the MultiPV set // using a statistical rule dependent on SkillLevel. Idea by Heinz van Saanen. @@ -2164,15 +2138,34 @@ void Thread::idle_loop(SplitPoint* sp) { } -// do_uci_async_cmd() is called by listener thread when in async mode and 'cmd' -// input line is received from the GUI. +// ThreadsManager::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 the UCI protocol: When pondering, the engine is not allowed to give a +// "bestmove" before the GUI sends it a "stop" or "ponderhit" command. +// We simply wait here until one of these commands (that raise StopRequest) is +// sent, and return, after which the bestmove and pondermove will be printed. -void do_uci_async_cmd(const std::string& cmd) { +void ThreadsManager::wait_for_stop_or_ponderhit() { - if (cmd == "quit") - QuitRequest = StopRequest = true; + StopOnPonderhit = true; - else if (cmd == "stop") + Thread& main = threads[0]; + + lock_grab(&main.sleepLock); + + while (!StopRequest) + cond_wait(&main.sleepCond, &main.sleepLock); + + lock_release(&main.sleepLock); +} + + +// uci_async_command() is called when a 'cmd' input line is received from the +// GUI while searching. + +void uci_async_command(const std::string& cmd) { + + if (cmd == "quit" || cmd == "stop") StopRequest = true; else if (cmd == "ponderhit") diff --git a/src/search.h b/src/search.h index 757aeb00..3262c757 100644 --- a/src/search.h +++ b/src/search.h @@ -20,11 +20,11 @@ #if !defined(SEARCH_H_INCLUDED) #define SEARCH_H_INCLUDED -#include - #include "move.h" #include "types.h" +#include + class Position; struct SplitPoint; @@ -53,21 +53,19 @@ struct SearchStack { struct SearchLimits { - SearchLimits() { memset(this, 0, sizeof(SearchLimits)); } - - SearchLimits(int t, int i, int mtg, int mt, int md, int mn, bool inf, bool pon) - : time(t), increment(i), movesToGo(mtg), maxTime(mt), maxDepth(md), - maxNodes(mn), infinite(inf), ponder(pon) {} - bool useTimeManagement() const { return !(maxTime | maxDepth | maxNodes | infinite); } int time, increment, movesToGo, maxTime, maxDepth, maxNodes, infinite, ponder; }; +extern SearchLimits Limits; +extern std::vector SearchMoves; +extern Position* RootPosition; + 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 think(); +extern void uci_async_command(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 4a37d393..57faa6e9 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -38,10 +38,10 @@ namespace { extern "C" { void* start_routine(void* thread) { #endif - if (((Thread*)thread)->threadID == MAX_THREADS) - ((Thread*)thread)->listener_loop(); + if (((Thread*)thread)->threadID == 0) + ((Thread*)thread)->main_loop(); - else if (((Thread*)thread)->threadID == MAX_THREADS + 1) + else if (((Thread*)thread)->threadID == MAX_THREADS) ((Thread*)thread)->timer_loop(); else ((Thread*)thread)->idle_loop(NULL); @@ -124,7 +124,7 @@ void ThreadsManager::set_size(int cnt) { activeThreads = cnt; - for (int i = 0; i < MAX_THREADS; i++) + for (int i = 1; i < MAX_THREADS; i++) // Ignore main thread if (i < activeThreads) { // Dynamically allocate pawn and material hash tables according to the @@ -147,14 +147,14 @@ void ThreadsManager::set_size(int cnt) { void ThreadsManager::init() { - // Initialize sleep condition used to block waiting for GUI input + // Initialize sleep condition used to block waiting for end of searching cond_init(&sleepCond); // Initialize threads lock, used when allocating slaves during splitting lock_init(&threadsLock); // Initialize sleep and split point locks - for (int i = 0; i < MAX_THREADS + 2; i++) + for (int i = 0; i <= MAX_THREADS; i++) { lock_init(&threads[i].sleepLock); cond_init(&threads[i].sleepCond); @@ -164,15 +164,14 @@ void ThreadsManager::init() { } // Initialize main thread's associated data - threads[0].is_searching = true; - threads[0].threadID = 0; - set_size(1); // This makes all the threads but the main to go to sleep + threads[0].pawnTable.init(); + threads[0].materialTable.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 + 2; i++) + // Create and launch all the threads, threads will go immediately to sleep + for (int i = 0; i <= MAX_THREADS; i++) { threads[i].is_searching = false; + threads[i].do_sleep = true; threads[i].threadID = i; #if defined(_MSC_VER) @@ -195,21 +194,18 @@ void ThreadsManager::init() { void ThreadsManager::exit() { - for (int i = 0; i < MAX_THREADS + 2; i++) + for (int i = 0; i <= MAX_THREADS; i++) { - if (i != 0) - { - threads[i].do_terminate = true; - threads[i].wake_up(); + threads[i].do_terminate = true; + threads[i].wake_up(); - // Wait for slave termination + // Wait for slave termination #if defined(_MSC_VER) - WaitForSingleObject(threads[i].handle, 0); - CloseHandle(threads[i].handle); + WaitForSingleObject(threads[i].handle, 0); + CloseHandle(threads[i].handle); #else - pthread_join(threads[i].handle, NULL); + pthread_join(threads[i].handle, NULL); #endif - } // Now we can safely destroy locks and wait conditions lock_destroy(&threads[i].sleepLock); @@ -387,7 +383,7 @@ void Thread::timer_loop() { void ThreadsManager::set_timer(int msec) { - Thread& timer = threads[MAX_THREADS + 1]; + Thread& timer = threads[MAX_THREADS]; lock_grab(&timer.sleepLock); timer.maxPly = msec; @@ -396,113 +392,57 @@ void ThreadsManager::set_timer(int msec) { } -// 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(). +// Thread::main_loop() is where the main thread is parked waiting to be started +// when there is a new search. Main thread will launch all the slave threads. -void Thread::listener_loop() { - - std::string cmd; +void Thread::main_loop() { while (true) { lock_grab(&sleepLock); - Threads.inputLine = cmd; - do_sleep = !is_searching; + do_sleep = true; // Always return to sleep after a search - // Here the thread is parked in sync mode after a line has been read - while (do_sleep && !do_terminate) // Catches spurious wake ups + is_searching = false; + + while (do_sleep && !do_terminate) { - cond_signal(&Threads.sleepCond); // Wake up main thread - cond_wait(&sleepCond, &sleepLock); // Sleep here + cond_signal(&Threads.sleepCond); // Wake up UI thread if needed + cond_wait(&sleepCond, &sleepLock); } + is_searching = true; + lock_release(&sleepLock); if (do_terminate) return; - if (!std::getline(std::cin, cmd)) // Block waiting for input - cmd = "quit"; - - lock_grab(&sleepLock); - - // If we are in async mode then process the command now - if (is_searching) - { - // Command "quit" is the last one received by the GUI, so park the - // thread waiting for exiting. Also, after a "stop", for instance on a - // ponder miss, GUI can immediately send the new position to search, - // so return to in-sync mode to avoid discarding good data. - if (cmd == "quit" || cmd == "stop") - is_searching = false; - - do_uci_async_cmd(cmd); - cmd = ""; // Input has been consumed - } - - lock_release(&sleepLock); + think(); // Search entry point } } -// ThreadsManager::getline() is used by main thread to block and wait for input, -// the behaviour mimics std::getline(). +// ThreadsManager::start_thinking() is used by UI thread to wake up the main +// thread parked in main_loop() and starting a new search. If asyncMode is true +// then function returns immediately, otherwise caller is blocked waiting for +// the search to finish. -void ThreadsManager::getline(std::string& cmd) { +void ThreadsManager::start_thinking(bool asyncMode) { - Thread& listener = threads[MAX_THREADS]; + Thread& main = threads[0]; - lock_grab(&listener.sleepLock); + lock_grab(&main.sleepLock); - listener.is_searching = false; // Set sync mode + // Wait main thread has finished before to launch a new search + while (!main.do_sleep) + cond_wait(&sleepCond, &main.sleepLock); - // If there is already some input to grab then skip without to wake up the - // listener. This can happen if after we send the "bestmove", the GUI sends - // a command that the listener buffers in inputLine before going to sleep. - if (inputLine.empty()) - { - listener.do_sleep = false; - cond_signal(&listener.sleepCond); // Wake up listener thread + main.do_sleep = false; + cond_signal(&main.sleepCond); // Wake up main thread - while (!listener.do_sleep) - cond_wait(&sleepCond, &listener.sleepLock); // Wait for input - } + if (!asyncMode) + cond_wait(&sleepCond, &main.sleepLock); - cmd = inputLine; - inputLine = ""; // Input has been consumed - - lock_release(&listener.sleepLock); -} - - -// ThreadsManager::start_listener() is called at the beginning of the search to -// swith from sync behaviour (default) to async and so be able to read from UCI -// while other threads are searching. This avoids main thread polling for input. - -void ThreadsManager::start_listener() { - - Thread& listener = threads[MAX_THREADS]; - - lock_grab(&listener.sleepLock); - listener.is_searching = true; - listener.do_sleep = false; - cond_signal(&listener.sleepCond); // Wake up listener thread - lock_release(&listener.sleepLock); -} - - -// ThreadsManager::stop_listener() is called before to send "bestmove" to GUI to -// return to in-sync behaviour. This is needed because while in async mode any -// command is discarded without being processed (except for a very few ones). - -void ThreadsManager::stop_listener() { - - Thread& listener = threads[MAX_THREADS]; - - lock_grab(&listener.sleepLock); - listener.is_searching = false; - lock_release(&listener.sleepLock); + lock_release(&main.sleepLock); } diff --git a/src/thread.h b/src/thread.h index e6d40094..df42c5aa 100644 --- a/src/thread.h +++ b/src/thread.h @@ -27,6 +27,7 @@ #include "movepick.h" #include "pawns.h" #include "position.h" +#include "search.h" const int MAX_THREADS = 32; const int MAX_ACTIVE_SPLIT_POINTS = 8; @@ -69,7 +70,7 @@ struct Thread { bool cutoff_occurred() const; bool is_available_to(int master) const; void idle_loop(SplitPoint* sp); - void listener_loop(); + void main_loop(); void timer_loop(); SplitPoint splitPoints[MAX_ACTIVE_SPLIT_POINTS]; @@ -116,10 +117,9 @@ public: bool available_slave_exists(int master) const; bool split_point_finished(SplitPoint* sp) const; - void getline(std::string& cmd); - void start_listener(); - void stop_listener(); + void start_thinking(bool asyncMode = true); void set_timer(int msec); + void wait_for_stop_or_ponderhit(); template Value split(Position& pos, SearchStack* ss, Value alpha, Value beta, Value bestValue, @@ -134,7 +134,6 @@ private: int activeThreads; bool useSleepingThreads; WaitCondition sleepCond; - std::string inputLine; }; extern ThreadsManager Threads; diff --git a/src/uci.cpp b/src/uci.cpp index ff804f97..ebb74d43 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -45,7 +45,7 @@ namespace { void set_option(istringstream& up); void set_position(Position& pos, istringstream& up); - bool go(Position& pos, istringstream& up); + void go(Position& pos, istringstream& up); void perft(Position& pos, istringstream& up); } @@ -61,22 +61,22 @@ void uci_loop() { string cmd, token; bool quit = false; - while (!quit) + while (!quit && getline(cin, cmd)) { - Threads.getline(cmd); - istringstream is(cmd); is >> skipws >> token; - if (token == "quit") - quit = true; + quit = (token == "quit"); - else if (token == "stop") - { /* avoid to reply "Unknown command: stop" */ } + if (token == "quit" || token == "stop" || token == "ponderhit") + { + uci_async_command(token); + Threads[0].wake_up(); // In case is waiting for stop or ponderhit + } else if (token == "go") - quit = !go(pos, is); + go(pos, is); else if (token == "ucinewgame") pos.from_fen(StarFEN, false); @@ -190,19 +190,21 @@ namespace { // string, and then calls think(). Returns false if a quit command // is received while thinking, true otherwise. - bool go(Position& pos, istringstream& is) { + void go(Position& pos, istringstream& is) { string token; - SearchLimits limits; - std::vector searchMoves; int time[] = { 0, 0 }, inc[] = { 0, 0 }; + memset(&Limits, 0, sizeof(SearchLimits)); + SearchMoves.clear(); + RootPosition = &pos; + while (is >> token) { if (token == "infinite") - limits.infinite = true; + Limits.infinite = true; else if (token == "ponder") - limits.ponder = true; + Limits.ponder = true; else if (token == "wtime") is >> time[WHITE]; else if (token == "btime") @@ -212,23 +214,23 @@ namespace { else if (token == "binc") is >> inc[BLACK]; else if (token == "movestogo") - is >> limits.movesToGo; + is >> Limits.movesToGo; else if (token == "depth") - is >> limits.maxDepth; + is >> Limits.maxDepth; else if (token == "nodes") - is >> limits.maxNodes; + is >> Limits.maxNodes; else if (token == "movetime") - is >> limits.maxTime; + is >> Limits.maxTime; else if (token == "searchmoves") while (is >> token) - searchMoves.push_back(move_from_uci(pos, token)); + SearchMoves.push_back(move_from_uci(pos, token)); } - searchMoves.push_back(MOVE_NONE); - limits.time = time[pos.side_to_move()]; - limits.increment = inc[pos.side_to_move()]; + SearchMoves.push_back(MOVE_NONE); + Limits.time = time[pos.side_to_move()]; + Limits.increment = inc[pos.side_to_move()]; - return think(pos, limits, &searchMoves[0]); + Threads.start_thinking(); }