1
0
Fork 0
mirror of https://github.com/sockspls/badfish synced 2025-04-29 16:23:09 +00:00

Rewrite async I/O

Use the starting thread to wait for GUI input and instead use
the other threads to search. The consequence is that now think()
is alwasy started on a differnt thread than the caller that
returns immediately waiting for input. This reformat greatly
simplifies the code and is more in line with the common way
to implement this feature.

As a side effect now we don't need anymore Makefile tricks
with sleep() to allow profile builds.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
This commit is contained in:
Marco Costalba 2011-11-23 20:07:29 +01:00
parent e9dc2e9e1e
commit ed04c010eb
7 changed files with 134 additions and 203 deletions

View file

@ -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

View file

@ -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<string> 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();
}
}

View file

@ -43,6 +43,10 @@ using std::cout;
using std::endl;
using std::string;
SearchLimits Limits;
std::vector<Move> 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<bool>());
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")

View file

@ -20,11 +20,11 @@
#if !defined(SEARCH_H_INCLUDED)
#define SEARCH_H_INCLUDED
#include <cstring>
#include "move.h"
#include "types.h"
#include <vector>
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<Move> 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)

View file

@ -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);
}

View file

@ -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 <bool Fake>
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;

View file

@ -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<Move> 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();
}