The grand design

Discussion of chess software programming and technical issues.

Moderator: Ras

SuneF
Posts: 127
Joined: Thu Sep 17, 2009 11:19 am

Re: The grand design

Post by SuneF »

michiguel wrote: The "search" thread, which is an iterative deepening thread is launched when needed. Every 512 nodes (IIRC) checks if it is out of time, or if a stop_flag is TRUE. If stop_flag == TRUE, it unwinds. The search thread does not check for input at all. It just received global flag "communication signals" from the main thread to know when to stop.

So, the main thread do all the organization work. For instance, it launches a search thread if required and waits for input. The input could be anything and will be processed accordingly (e.g. display a board, help, xboard commands etc.). If it is newgame, it will stop the search thread with stop_flag = TRUE + pthread_join_searchthread() and launches a new search thread.

Miguel
So your main thread does both input and output?
Or is the searching thread responsible for the output?
SuneF
Posts: 127
Joined: Thu Sep 17, 2009 11:19 am

Re: The grand design

Post by SuneF »

Aleks Peshkov wrote:

Code: Select all

class Uci {
    Root root; //chess position to analyze

    //set when child thinking thread is started
    //reset when search thread returned search result and going to finish
    volatile bool isThinking;

    //search thread is polling this variable
    volatile bool stopSignal;

    thread::Handle thinker;

    //output handling
    std::ostream* out;

    //input handling
    std::istringstream command; //current command line
    std::string token; //current token from the command line

 public:
    Uci () : isThinking(false) {}
   ~Uci () { stop(); }

    void operator() (std::istream& = std::cin, std::ostream& = std::cout);
};

void Uci::wait() {
    if (isThinking) {
        thread::join(thinker);
    }
    assert (!isThinking);
}

void Uci::stop() {
    if (isThinking) {
        stopSignal = true;
        wait();
    }
    assert (!isThinking);
}

void Uci::readyok() {
    *out << "readyok\n" << std::flush;
}

void Uci::operator() (istream& input, ostream& output) {
    out = &output;

    for (std::string line; std::getline(input, line); ) {
        command.clear(); //clear stream status from the previous command
        command.str(line);

        if (command >> token) {
            if      (token == "position")   { position(); }
            else if (token == "go")         { go(); }
            else if (token == "stop")       { stop(); }
            else if (token == "isready")    { readyok(); }
            else if (token == "quit")       { break; }
            else                            { unknown_command(); }
        }
    }
}
Nice code.
Could be inspired by this. :)
User avatar
michiguel
Posts: 6401
Joined: Thu Mar 09, 2006 8:30 pm
Location: Chicago, Illinois, USA

Re: The grand design

Post by michiguel »

SuneF wrote:
michiguel wrote: The "search" thread, which is an iterative deepening thread is launched when needed. Every 512 nodes (IIRC) checks if it is out of time, or if a stop_flag is TRUE. If stop_flag == TRUE, it unwinds. The search thread does not check for input at all. It just received global flag "communication signals" from the main thread to know when to stop.

So, the main thread do all the organization work. For instance, it launches a search thread if required and waits for input. The input could be anything and will be processed accordingly (e.g. display a board, help, xboard commands etc.). If it is newgame, it will stop the search thread with stop_flag = TRUE + pthread_join_searchthread() and launches a new search thread.

Miguel
So your main thread does both input and output?
Or is the searching thread responsible for the output?
The output related to the search is responsibility of the search thread. For instance, to send a move to the GUI.

Miguel
Harald
Posts: 318
Joined: Thu Mar 09, 2006 1:07 am

Re: The grand design

Post by Harald »

SuneF wrote:
Harald wrote: I have two threads. One only waits for input (winboard style) and the other
does everything else. That is the game, the main loop and the search.
Deep inside the search the global input variables from the other thread
are tested every x nodes. I am not sure whether the search should be a third
thread or the main program and input should be in one thread and the
search in the second one.

Another strange detail of my implementation is the uncommon use of
exceptions. When I test the node count, time condition or input deep in
the search I can handle some requests immediately and continue the search.
Other inputs throw an exception that is captured in the iterative deepening loop
or the main loop of the program or it leads to exit. With this I don't have to write
if ( some_break_condition ) return;
after each call to search() in the search tree. The code looks nice but is it slow?

Harald
Seems logical and simpler because you don't have to start and stop threads for every search and there is no syncronizing with the result from a search.
How do you handle if you get a move in the middle of a pondering search?
The stdin thread would get a move, tell the engine to stop searching and then put the move in a command queue, or would the stdin thread wait until the engine has stopped and then make the move, or what? How do you handle this?
When I get the opponent's move during a pondering search the search will
stop (throw exception) and return to the main loop of the engine (catch).
There I will do the move and start a search for my own move with the
normal time allocation. I don't care about ponder hit or not because I do
a ponder search for the opponent's move and not for my answer after a best
estimated opponent's move. I do not clear the hash table and that is the
only gain from pondering that I have.
I can break the search at any time and have a best pv move and I never
wait for a complete ply in the iterative deepening loop.

Harald
Sven
Posts: 4052
Joined: Thu May 15, 2008 9:57 pm
Location: Berlin, Germany
Full name: Sven Schüle

Re: The grand design

Post by Sven »

SuneF wrote:Also I need to figure out a way to poll for input. It's not obvious how to do that since a Node is not allowed to call back up the hierarchy into Protocol stuff. I don't see any other solution than to go threaded.
In my single-threaded, single-CPU approach I do this with an "event handler interface" class which has nothing but one (pure) virtual method "handleEvents()", is implemented by the class that deals with the protocol (in my case "WBEngine"), and is used both by the WBEngine and by the Searcher object. "handleEvents()" reads and processes one command from stdin. The name may be misleading, the name "handleEvent" would be better. When the Searcher decides that it is now time to poll for input it checks "isInputAvailable()" and if this returns true it calls "pEventHandler->handleEvents()" where pEventHandler is a pointer which has been passed to the constructor of the Searcher who stored it.

The virtual function call overhead is negligible in this case since "handleEvents()" is in fact called very rarely (at most once every N nodes, e.g. N=4096, but only if there is really input available). It is in fact the only case of using a virtual function in the release version of my engine, and it does absolutely no harm but helps to keep the design clean.

(The DEBUG version has another one with a similar idea, "onAssert()", which is used to dump the whole state of the engine top-down before terminating if an ASSERT() fires; the constructor of the topmost class registers 'this' as an "AssertionFailureInterface" at a central place, which is nothing but a static pointer variable over which the "onAssert()" is called by the assertionFailure() function - note: DEBUG version only!)

A similar design is possible for a multi-CPU engine. Since most probably you do not want each single search thread to perform polling for input in parallel, you can assign this job to exactly one of these threads somehow, and do as described above. In many cases the commands coming in during search will cause the search to stop anyway, which is kind of a "global" action and does not require some decentralized approach connected with many sorts of synchronization problems.

With this approach I avoid "illegal" interface access from low level to high level parts, which is crucial for a good software design.

What do you think about it?

Best regards,
Sven