从 Stockfish 合并更多 thread 代码
This commit is contained in:
parent
60e753be3c
commit
22b38ddd1d
|
@ -75,6 +75,7 @@ std::ostream &operator<<(std::ostream &os, Term t)
|
||||||
|
|
||||||
os << " | " << scores[t][WHITE] - scores[t][BLACK] << "\n";
|
os << " | " << scores[t][WHITE] - scores[t][BLACK] << "\n";
|
||||||
#endif
|
#endif
|
||||||
|
t = t;
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -213,5 +214,6 @@ std::string Eval::trace(Position *pos)
|
||||||
|
|
||||||
return ss.str();
|
return ss.str();
|
||||||
#endif
|
#endif
|
||||||
|
pos = pos;
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
|
@ -134,6 +134,7 @@ namespace CTSL //Concurrent Thread Safe Library
|
||||||
void resize(size_t o)
|
void resize(size_t o)
|
||||||
{
|
{
|
||||||
// TODO
|
// TODO
|
||||||
|
o = o;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -139,6 +139,10 @@ Position &Position::set(const string &fenStr, StateInfo *si, Thread *th)
|
||||||
|
|
||||||
assert(pos_is_ok());
|
assert(pos_is_ok());
|
||||||
#endif
|
#endif
|
||||||
|
th = th;
|
||||||
|
si = si;
|
||||||
|
string str = fenStr;
|
||||||
|
str = "";
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,6 +166,7 @@ void Position::set_state(StateInfo *si) const
|
||||||
if (sideToMove == BLACK)
|
if (sideToMove == BLACK)
|
||||||
si->key ^= Zobrist::side;
|
si->key ^= Zobrist::side;
|
||||||
#endif
|
#endif
|
||||||
|
si = si;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -188,6 +193,10 @@ Position &Position::set(const string &code, Color c, StateInfo *si)
|
||||||
|
|
||||||
return set(fenStr, si, nullptr);
|
return set(fenStr, si, nullptr);
|
||||||
#endif
|
#endif
|
||||||
|
si = si;
|
||||||
|
c = c;
|
||||||
|
string ccc = code;
|
||||||
|
ccc = "";
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
169
src/search.cpp
169
src/search.cpp
|
@ -17,20 +17,31 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <cstring> // For std::memset
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "evaluate.h"
|
||||||
|
#include "misc.h"
|
||||||
|
#include "movegen.h"
|
||||||
|
#include "movepick.h"
|
||||||
|
#include "position.h"
|
||||||
|
#include "search.h"
|
||||||
|
#include "thread.h"
|
||||||
|
#include "timeman.h"
|
||||||
|
#include "tt.h"
|
||||||
|
#include "uci.h"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include "search.h"
|
|
||||||
#include "evaluate.h"
|
|
||||||
#include "hashmap.h"
|
#include "hashmap.h"
|
||||||
#include "tt.h"
|
|
||||||
#include "endgame.h"
|
#include "endgame.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "option.h"
|
#include "option.h"
|
||||||
#include "misc.h"
|
|
||||||
#include "movepick.h"
|
|
||||||
|
|
||||||
using namespace CTSL;
|
using namespace CTSL;
|
||||||
|
|
||||||
|
@ -39,6 +50,8 @@ namespace Search
|
||||||
LimitsType Limits;
|
LimitsType Limits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using namespace Search;
|
||||||
|
|
||||||
vector<Key> moveHistory;
|
vector<Key> moveHistory;
|
||||||
|
|
||||||
AIAlgorithm::AIAlgorithm()
|
AIAlgorithm::AIAlgorithm()
|
||||||
|
@ -738,4 +751,146 @@ void Search::clear()
|
||||||
{
|
{
|
||||||
// TODO
|
// TODO
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// MainThread::search() is started when the program receives the UCI 'go'
|
||||||
|
/// command. It searches from the root position and outputs the "bestmove".
|
||||||
|
|
||||||
|
void MainThread::search()
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
#if 0
|
||||||
|
if (Limits.perft) {
|
||||||
|
nodes = perft<true>(rootPos, Limits.perft);
|
||||||
|
sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color us = rootPos.side_to_move();
|
||||||
|
Time.init(Limits, us, rootPos.game_ply());
|
||||||
|
TT.new_search();
|
||||||
|
|
||||||
|
if (rootMoves.empty()) {
|
||||||
|
rootMoves.emplace_back(MOVE_NONE);
|
||||||
|
sync_cout << "info depth 0 score "
|
||||||
|
<< UCI::value(false /* TODO */ ? -VALUE_MATE : VALUE_DRAW)
|
||||||
|
<< sync_endl;
|
||||||
|
} else {
|
||||||
|
for (Thread *th : Threads) {
|
||||||
|
th->bestMoveChanges = 0;
|
||||||
|
if (th != this)
|
||||||
|
th->start_searching();
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread::search(); // Let's start searching!
|
||||||
|
}
|
||||||
|
|
||||||
|
// When we reach the maximum depth, we can arrive here without a raise of
|
||||||
|
// Threads.stop. However, if we are pondering or in an infinite search,
|
||||||
|
// the UCI protocol states that we shouldn't print the best move before the
|
||||||
|
// GUI sends a "stop" or "ponderhit" command. We therefore simply wait here
|
||||||
|
// until the GUI sends one of those commands.
|
||||||
|
|
||||||
|
while (!Threads.stop && (ponder || Limits.infinite)) {
|
||||||
|
} // Busy wait for a stop or a ponder reset
|
||||||
|
|
||||||
|
// Stop the threads if not already stopped (also raise the stop if
|
||||||
|
// "ponderhit" just reset Threads.ponder).
|
||||||
|
Threads.stop = true;
|
||||||
|
|
||||||
|
// Wait until all threads have finished
|
||||||
|
for (Thread *th : Threads)
|
||||||
|
if (th != this)
|
||||||
|
th->wait_for_search_finished();
|
||||||
|
|
||||||
|
// When playing in 'nodes as time' mode, subtract the searched nodes from
|
||||||
|
// the available ones before exiting.
|
||||||
|
if (Limits.npmsec)
|
||||||
|
Time.availableNodes += Limits.inc[us] - Threads.nodes_searched();
|
||||||
|
|
||||||
|
Thread *bestThread = this;
|
||||||
|
|
||||||
|
// Check if there are threads with a better score than main thread
|
||||||
|
if (Options["MultiPV"] == 1
|
||||||
|
&& !Limits.depth
|
||||||
|
&& !(Skill((int)Options["Skill Level"]).enabled() || Options["UCI_LimitStrength"])
|
||||||
|
&& rootMoves[0].pv[0] != MOVE_NONE) {
|
||||||
|
std::map<Move, int64_t> votes;
|
||||||
|
Value minScore = this->rootMoves[0].score;
|
||||||
|
|
||||||
|
// Find minimum score
|
||||||
|
for (Thread *th : Threads)
|
||||||
|
minScore = std::min(minScore, th->rootMoves[0].score);
|
||||||
|
|
||||||
|
// Vote according to score and depth, and select the best thread
|
||||||
|
for (Thread *th : Threads) {
|
||||||
|
votes[th->rootMoves[0].pv[0]] +=
|
||||||
|
(th->rootMoves[0].score - minScore + 14) * int(th->completedDepth);
|
||||||
|
|
||||||
|
if (abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY) {
|
||||||
|
// Make sure we pick the shortest mate / TB conversion or stave off mate the longest
|
||||||
|
if (th->rootMoves[0].score > bestThread->rootMoves[0].score)
|
||||||
|
bestThread = th;
|
||||||
|
} else if (th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY
|
||||||
|
|| (th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY
|
||||||
|
&& votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]]))
|
||||||
|
bestThread = th;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bestPreviousScore = bestThread->rootMoves[0].score;
|
||||||
|
|
||||||
|
// Send again PV info if we have a new best thread
|
||||||
|
if (bestThread != this)
|
||||||
|
sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE) << sync_endl;
|
||||||
|
|
||||||
|
sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0]);
|
||||||
|
|
||||||
|
if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos))
|
||||||
|
std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1]);
|
||||||
|
|
||||||
|
std::cout << sync_endl;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Thread::search() is the main iterative deepening loop. It calls search()
|
||||||
|
/// repeatedly with increasing depth until the allocated thinking time has been
|
||||||
|
/// consumed, the user stops the search, or the maximum search depth is reached.
|
||||||
|
|
||||||
|
void Thread::search()
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MainThread::check_time() is used to print debug info and, more importantly,
|
||||||
|
/// to detect when we are out of available time and thus stop the search.
|
||||||
|
|
||||||
|
void MainThread::check_time()
|
||||||
|
{
|
||||||
|
|
||||||
|
if (--callsCnt > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// When using nodes, ensure checking rate is not lower than 0.1% of nodes
|
||||||
|
callsCnt = Limits.nodes ? std::min(1024, int(Limits.nodes / 1024)) : 1024;
|
||||||
|
|
||||||
|
static TimePoint lastInfoTime = now();
|
||||||
|
|
||||||
|
TimePoint elapsed = Time.elapsed();
|
||||||
|
TimePoint tick = Limits.startTime + elapsed;
|
||||||
|
|
||||||
|
if (tick - lastInfoTime >= 1000) {
|
||||||
|
lastInfoTime = tick;
|
||||||
|
dbg_print();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should not stop pondering until told so by the GUI
|
||||||
|
if (ponder)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((Limits.use_time_management() && (elapsed > Time.maximum() - 10 || stopOnPonderhit))
|
||||||
|
|| (Limits.movetime && elapsed >= Limits.movetime)
|
||||||
|
|| (Limits.nodes && Threads.nodes_searched() >= (uint64_t)Limits.nodes))
|
||||||
|
Threads.stop = true;
|
||||||
|
}
|
||||||
|
|
54
src/search.h
54
src/search.h
|
@ -47,6 +47,60 @@ using namespace CTSL;
|
||||||
|
|
||||||
namespace Search
|
namespace Search
|
||||||
{
|
{
|
||||||
|
/// Threshold used for countermoves based pruning
|
||||||
|
constexpr int CounterMovePruneThreshold = 0;
|
||||||
|
|
||||||
|
|
||||||
|
/// Stack struct keeps track of the information we need to remember from nodes
|
||||||
|
/// shallower and deeper in the tree during the search. Each search thread has
|
||||||
|
/// its own array of Stack objects, indexed by the current ply.
|
||||||
|
|
||||||
|
struct Stack
|
||||||
|
{
|
||||||
|
Move *pv;
|
||||||
|
int ply;
|
||||||
|
Move currentMove;
|
||||||
|
Move excludedMove;
|
||||||
|
Move killers[2];
|
||||||
|
Value staticEval;
|
||||||
|
int statScore;
|
||||||
|
int moveCount;
|
||||||
|
bool inCheck;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// RootMove struct is used for moves at the root of the tree. For each root move
|
||||||
|
/// we store a score and a PV (really a refutation in the case of moves which
|
||||||
|
/// fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves.
|
||||||
|
|
||||||
|
struct RootMove
|
||||||
|
{
|
||||||
|
|
||||||
|
explicit RootMove(Move m) : pv(1, m)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
bool operator==(const Move &m) const
|
||||||
|
{
|
||||||
|
return pv[0] == m;
|
||||||
|
}
|
||||||
|
bool operator<(const RootMove &m) const
|
||||||
|
{ // Sort in descending order
|
||||||
|
return m.score != score ? m.score < score
|
||||||
|
: m.previousScore < previousScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value score = -VALUE_INFINITE;
|
||||||
|
Value previousScore = -VALUE_INFINITE;
|
||||||
|
int selDepth = 0;
|
||||||
|
int tbRank = 0;
|
||||||
|
int bestMoveCount = 0;
|
||||||
|
Value tbScore;
|
||||||
|
std::vector<Move> pv;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::vector<RootMove> RootMoves;
|
||||||
|
|
||||||
|
|
||||||
/// LimitsType struct stores information sent by GUI about available time to
|
/// LimitsType struct stores information sent by GUI about available time to
|
||||||
/// search the current move, maximum depth/time, or if we are in analysis mode.
|
/// search the current move, maximum depth/time, or if we are in analysis mode.
|
||||||
|
|
||||||
|
|
224
src/thread.cpp
224
src/thread.cpp
|
@ -1,16 +1,16 @@
|
||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Fishmill, a UCI Mill Game playing engine derived from Stockfish
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish author)
|
||||||
Copyright (C) 2015-2019 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish author)
|
||||||
Copyright (C) 2019-2020 Calcitem <calcitem@outlook.com>
|
Copyright (C) 2020 Calcitem <calcitem@outlook.com>
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Fishmill is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
Stockfish is distributed in the hope that it will be useful,
|
Fishmill is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
@ -25,6 +25,7 @@
|
||||||
#include "movegen.h"
|
#include "movegen.h"
|
||||||
#include "search.h"
|
#include "search.h"
|
||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
|
#include "uci.h"
|
||||||
#include "tt.h"
|
#include "tt.h"
|
||||||
|
|
||||||
ThreadPool Threads; // Global object
|
ThreadPool Threads; // Global object
|
||||||
|
@ -33,161 +34,184 @@ ThreadPool Threads; // Global object
|
||||||
/// Thread constructor launches the thread and waits until it goes to sleep
|
/// Thread constructor launches the thread and waits until it goes to sleep
|
||||||
/// in idle_loop(). Note that 'searching' and 'exit' should be already set.
|
/// in idle_loop(). Note that 'searching' and 'exit' should be already set.
|
||||||
|
|
||||||
Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) {
|
Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this)
|
||||||
|
{
|
||||||
|
|
||||||
wait_for_search_finished();
|
wait_for_search_finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Thread destructor wakes up the thread in idle_loop() and waits
|
/// Thread destructor wakes up the thread in idle_loop() and waits
|
||||||
/// for its termination. Thread should be already waiting.
|
/// for its termination. Thread should be already waiting.
|
||||||
|
|
||||||
Thread::~Thread() {
|
Thread::~Thread()
|
||||||
|
{
|
||||||
|
|
||||||
assert(!searching);
|
assert(!searching);
|
||||||
|
|
||||||
exit = true;
|
exit = true;
|
||||||
start_searching();
|
start_searching();
|
||||||
stdThread.join();
|
stdThread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Thread::bestMoveCount(Move move) return best move counter for the given root move
|
||||||
|
|
||||||
|
int Thread::best_move_count(Move move) const
|
||||||
|
{
|
||||||
|
|
||||||
|
auto rm = std::find(rootMoves.begin() + pvIdx,
|
||||||
|
rootMoves.begin() + pvLast, move);
|
||||||
|
|
||||||
|
return rm != rootMoves.begin() + pvLast ? rm->bestMoveCount : 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// Thread::clear() reset histories, usually before a new game
|
/// Thread::clear() reset histories, usually before a new game
|
||||||
|
|
||||||
void Thread::clear() {
|
void Thread::clear()
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Thread::start_searching() wakes up the thread that will start the search
|
/// Thread::start_searching() wakes up the thread that will start the search
|
||||||
|
|
||||||
void Thread::start_searching() {
|
void Thread::start_searching()
|
||||||
|
{
|
||||||
|
|
||||||
std::lock_guard<Mutex> lk(mutex);
|
std::lock_guard<std::mutex> lk(mutex);
|
||||||
searching = true;
|
searching = true;
|
||||||
cv.notify_one(); // Wake up the thread in idle_loop()
|
cv.notify_one(); // Wake up the thread in idle_loop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Thread::wait_for_search_finished() blocks on the condition variable
|
/// Thread::wait_for_search_finished() blocks on the condition variable
|
||||||
/// until the thread has finished searching.
|
/// until the thread has finished searching.
|
||||||
|
|
||||||
void Thread::wait_for_search_finished() {
|
void Thread::wait_for_search_finished()
|
||||||
|
{
|
||||||
|
|
||||||
std::unique_lock<Mutex> lk(mutex);
|
std::unique_lock<std::mutex> lk(mutex);
|
||||||
cv.wait(lk, [&]{ return !searching; });
|
cv.wait(lk, [&] { return !searching; });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Thread::idle_loop() is where the thread is parked, blocked on the
|
/// Thread::idle_loop() is where the thread is parked, blocked on the
|
||||||
/// condition variable, when it has no work to do.
|
/// condition variable, when it has no work to do.
|
||||||
|
|
||||||
void Thread::idle_loop() {
|
void Thread::idle_loop()
|
||||||
|
{
|
||||||
|
|
||||||
// If OS already scheduled us on a different group than 0 then don't overwrite
|
// If OS already scheduled us on a different group than 0 then don't overwrite
|
||||||
// the choice, eventually we are one of many one-threaded processes running on
|
// the choice, eventually we are one of many one-threaded processes running on
|
||||||
// some Windows NUMA hardware, for instance in fishtest. To make it simple,
|
// some Windows NUMA hardware, for instance in fishtest. To make it simple,
|
||||||
// just check if running threads are below a threshold, in this case all this
|
// just check if running threads are below a threshold, in this case all this
|
||||||
// NUMA machinery is not needed.
|
// NUMA machinery is not needed.
|
||||||
//if (Options["Threads"] > 8)
|
if (Options["Threads"] > 8)
|
||||||
// WinProcGroup::bindThisThread(idx);
|
WinProcGroup::bindThisThread(idx);
|
||||||
|
|
||||||
while (true)
|
while (true) {
|
||||||
{
|
std::unique_lock<std::mutex> lk(mutex);
|
||||||
std::unique_lock<Mutex> lk(mutex);
|
searching = false;
|
||||||
searching = false;
|
cv.notify_one(); // Wake up anyone waiting for search finished
|
||||||
cv.notify_one(); // Wake up anyone waiting for search finished
|
cv.wait(lk, [&] { return searching; });
|
||||||
cv.wait(lk, [&]{ return searching; });
|
|
||||||
|
|
||||||
if (exit)
|
if (exit)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
lk.unlock();
|
lk.unlock();
|
||||||
|
|
||||||
//search();
|
search();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ThreadPool::set() creates/destroys threads to match the requested number.
|
/// ThreadPool::set() creates/destroys threads to match the requested number.
|
||||||
/// Created and launched threads will immediately go to sleep in idle_loop.
|
/// Created and launched threads will immediately go to sleep in idle_loop.
|
||||||
/// Upon resizing, threads are recreated to allow for binding if necessary.
|
/// Upon resizing, threads are recreated to allow for binding if necessary.
|
||||||
|
|
||||||
void ThreadPool::set(size_t requested) {
|
void ThreadPool::set(size_t requested)
|
||||||
|
{
|
||||||
|
|
||||||
if (size() > 0) { // destroy any existing thread(s)
|
if (size() > 0) { // destroy any existing thread(s)
|
||||||
main()->wait_for_search_finished();
|
main()->wait_for_search_finished();
|
||||||
|
|
||||||
while (size() > 0)
|
while (size() > 0)
|
||||||
delete back(), pop_back();
|
delete back(), pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requested > 0) { // create new thread(s)
|
if (requested > 0) { // create new thread(s)
|
||||||
push_back(new MainThread(0));
|
push_back(new MainThread(0));
|
||||||
|
|
||||||
while (size() < requested)
|
while (size() < requested)
|
||||||
push_back(new Thread(size()));
|
push_back(new Thread(size()));
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
// Reallocate the key with the new threadpool size
|
// Reallocate the hash with the new threadpool size
|
||||||
//TT.resize(Options["Hash"]);
|
TT.resize((size_t)Options["Hash"]);
|
||||||
}
|
|
||||||
|
// Init thread number dependent search params.
|
||||||
|
Search::init();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ThreadPool::clear() sets threadPool data to initial values.
|
/// ThreadPool::clear() sets threadPool data to initial values.
|
||||||
|
|
||||||
void ThreadPool::clear() {
|
void ThreadPool::clear()
|
||||||
|
{
|
||||||
|
|
||||||
for (Thread* th : *this)
|
for (Thread *th : *this)
|
||||||
th->clear();
|
th->clear();
|
||||||
|
|
||||||
main()->callsCnt = 0;
|
main()->callsCnt = 0;
|
||||||
main()->previousScore = VALUE_INFINITE;
|
main()->bestPreviousScore = VALUE_INFINITE;
|
||||||
main()->previousTimeReduction = 1.0;
|
main()->previousTimeReduction = 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and
|
/// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and
|
||||||
/// returns immediately. Main thread will wake up other threads and start the search.
|
/// returns immediately. Main thread will wake up other threads and start the search.
|
||||||
#if 0
|
|
||||||
void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
|
|
||||||
const Search::LimitsType& limits, bool ponderMode) {
|
|
||||||
main()->wait_for_search_finished();
|
|
||||||
|
|
||||||
main()->stopOnPonderhit = stop = false;
|
void ThreadPool::start_thinking(Position *pos, StateListPtr &states,
|
||||||
main()->ponder = ponderMode;
|
const Search::LimitsType &limits, bool ponderMode)
|
||||||
Search::Limits = limits;
|
{
|
||||||
Search::RootMoves rootMoves;
|
|
||||||
|
|
||||||
for (const auto& m : MoveList<LEGAL>(pos))
|
main()->wait_for_search_finished();
|
||||||
if ( limits.searchmoves.empty()
|
|
||||||
|| std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m))
|
|
||||||
rootMoves.emplace_back(m);
|
|
||||||
|
|
||||||
if (!rootMoves.empty())
|
main()->stopOnPonderhit = stop = false;
|
||||||
Tablebases::rank_root_moves(pos, rootMoves);
|
increaseDepth = true;
|
||||||
|
main()->ponder = ponderMode;
|
||||||
|
Search::Limits = limits;
|
||||||
|
Search::RootMoves rootMoves;
|
||||||
|
|
||||||
// After ownership transfer 'states' becomes empty, so if we stop the search
|
for (const auto &m : MoveList(pos))
|
||||||
// and call 'go' again without setting a new position states.get() == NULL.
|
if (limits.searchmoves.empty()
|
||||||
assert(states.get() || setupStates.get());
|
|| std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m))
|
||||||
|
rootMoves.emplace_back(m);
|
||||||
|
|
||||||
if (states.get())
|
#ifdef TBPROBE
|
||||||
setupStates = std::move(states); // Ownership transfer, states is now empty
|
if (!rootMoves.empty())
|
||||||
|
Tablebases::rank_root_moves(pos, rootMoves);
|
||||||
// We use Position::set() to set root position across threads. But there are
|
|
||||||
// some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot
|
|
||||||
// be deduced from a fen string, so set() clears them and to not lose the info
|
|
||||||
// we need to backup and later restore setupStates->back(). Note that setupStates
|
|
||||||
// is shared by threads but is accessed in read-only mode.
|
|
||||||
StateInfo tmp = setupStates->back();
|
|
||||||
|
|
||||||
for (Thread* th : *this)
|
|
||||||
{
|
|
||||||
th->shuffleExts = th->nodes = th->tbHits = th->nmpMinPly = 0;
|
|
||||||
th->rootDepth = th->completedDepth = DEPTH_ZERO;
|
|
||||||
th->rootMoves = rootMoves;
|
|
||||||
th->rootPos.set(pos.fen(), pos.is_chess960(), &setupStates->back(), th);
|
|
||||||
}
|
|
||||||
|
|
||||||
setupStates->back() = tmp;
|
|
||||||
|
|
||||||
main()->start_searching();
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// After ownership transfer 'states' becomes empty, so if we stop the search
|
||||||
|
// and call 'go' again without setting a new position states.get() == NULL.
|
||||||
|
assert(states.get() || setupStates.get());
|
||||||
|
|
||||||
|
if (states.get())
|
||||||
|
setupStates = std::move(states); // Ownership transfer, states is now empty
|
||||||
|
|
||||||
|
// We use Position::set() to set root position across threads. But there are
|
||||||
|
// some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot
|
||||||
|
// be deduced from a fen string, so set() clears them and to not lose the info
|
||||||
|
// we need to backup and later restore setupStates->back(). Note that setupStates
|
||||||
|
// is shared by threads but is accessed in read-only mode.
|
||||||
|
StateInfo tmp = setupStates->back();
|
||||||
|
|
||||||
|
for (Thread *th : *this) {
|
||||||
|
th->nodes = th->tbHits = th->nmpMinPly = 0;
|
||||||
|
th->rootDepth = th->completedDepth = 0;
|
||||||
|
th->rootMoves = rootMoves;
|
||||||
|
th->rootPos.set(pos->fen(), &setupStates->back(), th);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupStates->back() = tmp;
|
||||||
|
|
||||||
|
main()->start_searching();
|
||||||
|
}
|
||||||
|
|
121
src/thread.h
121
src/thread.h
|
@ -1,16 +1,16 @@
|
||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Fishmill, a UCI Mill Game playing engine derived from Stockfish
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish author)
|
||||||
Copyright (C) 2015-2019 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish author)
|
||||||
Copyright (C) 2019-2020 Calcitem <calcitem@outlook.com>
|
Copyright (C) 2020 Calcitem <calcitem@outlook.com>
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Fishmill is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
Stockfish is distributed in the hope that it will be useful,
|
Fishmill is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
@ -28,58 +28,64 @@
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "movepick.h"
|
||||||
#include "position.h"
|
#include "position.h"
|
||||||
#include "search.h"
|
#include "search.h"
|
||||||
#include "thread_win32_osx.h"
|
#include "thread_win32_osx.h"
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
class Position;
|
|
||||||
|
|
||||||
/// Thread class keeps together all the thread-related stuff. We use
|
/// Thread class keeps together all the thread-related stuff. We use
|
||||||
/// per-thread pawn and material key tables so that once we get a
|
/// per-thread pawn and material hash tables so that once we get a
|
||||||
/// pointer to an entry its life time is unlimited and we don't have
|
/// pointer to an entry its life time is unlimited and we don't have
|
||||||
/// to care about someone changing the entry under our feet.
|
/// to care about someone changing the entry under our feet.
|
||||||
|
|
||||||
class Thread {
|
class Thread
|
||||||
|
{
|
||||||
Mutex mutex;
|
std::mutex mutex;
|
||||||
ConditionVariable cv;
|
std::condition_variable cv;
|
||||||
size_t idx;
|
size_t idx;
|
||||||
bool exit = false, searching = true; // Set before starting std::thread
|
bool exit = false, searching = true; // Set before starting std::thread
|
||||||
NativeThread stdThread;
|
NativeThread stdThread;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Thread(size_t);
|
explicit Thread(size_t);
|
||||||
virtual ~Thread();
|
virtual ~Thread();
|
||||||
//virtual void search();
|
virtual void search();
|
||||||
void clear();
|
void clear();
|
||||||
void idle_loop();
|
void idle_loop();
|
||||||
void start_searching();
|
void start_searching();
|
||||||
void wait_for_search_finished();
|
void wait_for_search_finished();
|
||||||
|
int best_move_count(Move move) const;
|
||||||
|
|
||||||
size_t pvIdx, multiPV, pvLast, shuffleExts;
|
size_t pvIdx, pvLast;
|
||||||
int selDepth, nmpMinPly;
|
uint64_t ttHitAverage;
|
||||||
std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
|
int selDepth, nmpMinPly;
|
||||||
|
Color nmpColor;
|
||||||
|
std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
|
||||||
|
|
||||||
Position rootPos;
|
Position rootPos;
|
||||||
|
Search::RootMoves rootMoves;
|
||||||
|
Depth rootDepth, completedDepth;
|
||||||
|
Score contempt;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// MainThread is a derived class specific for main thread
|
/// MainThread is a derived class specific for main thread
|
||||||
|
|
||||||
struct MainThread : public Thread {
|
struct MainThread : public Thread
|
||||||
|
{
|
||||||
|
|
||||||
using Thread::Thread;
|
using Thread::Thread;
|
||||||
|
|
||||||
//void search() override;
|
void search() override;
|
||||||
//void check_time();
|
void check_time();
|
||||||
|
|
||||||
double previousTimeReduction;
|
double previousTimeReduction;
|
||||||
Value previousScore;
|
Value bestPreviousScore;
|
||||||
int callsCnt;
|
Value iterValue[4];
|
||||||
bool stopOnPonderhit;
|
int callsCnt;
|
||||||
std::atomic_bool ponder;
|
bool stopOnPonderhit;
|
||||||
|
std::atomic_bool ponder;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -87,28 +93,39 @@ struct MainThread : public Thread {
|
||||||
/// parking and, most importantly, launching a thread. All the access to threads
|
/// parking and, most importantly, launching a thread. All the access to threads
|
||||||
/// is done through this class.
|
/// is done through this class.
|
||||||
|
|
||||||
struct ThreadPool : public std::vector<Thread*> {
|
struct ThreadPool : public std::vector<Thread *>
|
||||||
|
{
|
||||||
|
|
||||||
//void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false);
|
void start_thinking(Position *, StateListPtr &, const Search::LimitsType &, bool = false);
|
||||||
void clear();
|
void clear();
|
||||||
void set(size_t);
|
void set(size_t);
|
||||||
|
|
||||||
MainThread* main() const { return static_cast<MainThread*>(front()); }
|
MainThread *main() const
|
||||||
uint64_t nodes_searched() const { return accumulate(&Thread::nodes); }
|
{
|
||||||
uint64_t tb_hits() const { return accumulate(&Thread::tbHits); }
|
return static_cast<MainThread *>(front());
|
||||||
|
}
|
||||||
|
uint64_t nodes_searched() const
|
||||||
|
{
|
||||||
|
return accumulate(&Thread::nodes);
|
||||||
|
}
|
||||||
|
uint64_t tb_hits() const
|
||||||
|
{
|
||||||
|
return accumulate(&Thread::tbHits);
|
||||||
|
}
|
||||||
|
|
||||||
std::atomic_bool stop;
|
std::atomic_bool stop, increaseDepth;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//StateListPtr setupStates;
|
StateListPtr setupStates;
|
||||||
|
|
||||||
uint64_t accumulate(std::atomic<uint64_t> Thread::* member) const {
|
uint64_t accumulate(std::atomic<uint64_t> Thread:: *member) const
|
||||||
|
{
|
||||||
|
|
||||||
uint64_t sum = 0;
|
uint64_t sum = 0;
|
||||||
for (Thread* th : *this)
|
for (Thread *th : *this)
|
||||||
sum += (th->*member).load(std::memory_order_relaxed);
|
sum += (th->*member).load(std::memory_order_relaxed);
|
||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
extern ThreadPool Threads;
|
extern ThreadPool Threads;
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Fishmill, a UCI Mill Game playing engine derived from Stockfish
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish author)
|
||||||
Copyright (C) 2015-2019 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish author)
|
||||||
|
Copyright (C) 2020 Calcitem <calcitem@outlook.com>
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Fishmill is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
Stockfish is distributed in the hope that it will be useful,
|
Fishmill is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
@ -21,86 +22,47 @@
|
||||||
#ifndef THREAD_WIN32_OSX_H_INCLUDED
|
#ifndef THREAD_WIN32_OSX_H_INCLUDED
|
||||||
#define THREAD_WIN32_OSX_H_INCLUDED
|
#define THREAD_WIN32_OSX_H_INCLUDED
|
||||||
|
|
||||||
/// STL thread library used by mingw and gcc when cross compiling for Windows
|
|
||||||
/// relies on libwinpthread. Currently libwinpthread implements mutexes directly
|
|
||||||
/// on top of Windows semaphores. Semaphores, being kernel objects, require kernel
|
|
||||||
/// mode transition in order to lock or unlock, which is very slow compared to
|
|
||||||
/// interlocked operations (about 30% slower on bench test). To work around this
|
|
||||||
/// issue, we define our wrappers to the low level Win32 calls. We use critical
|
|
||||||
/// sections to support Windows XP and older versions. Unfortunately, cond_wait()
|
|
||||||
/// is racy between unlock() and WaitForSingleObject() but they have the same
|
|
||||||
/// speed performance as the SRW locks.
|
|
||||||
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <mutex>
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#if defined(_WIN32) && !defined(_MSC_VER)
|
|
||||||
|
|
||||||
#ifndef NOMINMAX
|
|
||||||
# define NOMINMAX // Disable macros min() and max()
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
#include <windows.h>
|
|
||||||
#undef WIN32_LEAN_AND_MEAN
|
|
||||||
#undef NOMINMAX
|
|
||||||
|
|
||||||
/// Mutex and ConditionVariable struct are wrappers of the low level locking
|
|
||||||
/// machinery and are modeled after the corresponding C++11 classes.
|
|
||||||
|
|
||||||
struct Mutex {
|
|
||||||
Mutex() { InitializeCriticalSection(&cs); }
|
|
||||||
~Mutex() { DeleteCriticalSection(&cs); }
|
|
||||||
void lock() { EnterCriticalSection(&cs); }
|
|
||||||
void unlock() { LeaveCriticalSection(&cs); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
CRITICAL_SECTION cs;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::condition_variable_any ConditionVariable;
|
|
||||||
|
|
||||||
#else // Default case: use STL classes
|
|
||||||
|
|
||||||
typedef std::mutex Mutex;
|
|
||||||
typedef std::condition_variable ConditionVariable;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// On OSX threads other than the main thread are created with a reduced stack
|
/// On OSX threads other than the main thread are created with a reduced stack
|
||||||
/// size of 512KB by default, this is dangerously low for deep searches, so
|
/// size of 512KB by default, this is too low for deep searches, which require
|
||||||
/// adjust it to TH_STACK_SIZE. The implementation calls pthread_create() with
|
/// somewhat more than 1MB stack, so adjust it to TH_STACK_SIZE.
|
||||||
/// proper stack size parameter.
|
/// The implementation calls pthread_create() with the stack size parameter
|
||||||
|
/// equal to the linux 8MB default, on platforms that support it.
|
||||||
|
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__)
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
||||||
static const size_t TH_STACK_SIZE = 2 * 1024 * 1024;
|
static const size_t TH_STACK_SIZE = 8 * 1024 * 1024;
|
||||||
|
|
||||||
template <class T, class P = std::pair<T*, void(T::*)()>>
|
template <class T, class P = std::pair<T *, void(T:: *)()>>
|
||||||
void* start_routine(void* ptr)
|
void *start_routine(void *ptr)
|
||||||
{
|
{
|
||||||
P* p = reinterpret_cast<P*>(ptr);
|
P *p = reinterpret_cast<P *>(ptr);
|
||||||
(p->first->*(p->second))(); // Call member function pointer
|
(p->first->*(p->second))(); // Call member function pointer
|
||||||
delete p;
|
delete p;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
class NativeThread {
|
class NativeThread
|
||||||
|
{
|
||||||
|
|
||||||
pthread_t thread;
|
pthread_t thread;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
template<class T, class P = std::pair<T*, void(T::*)()>>
|
template<class T, class P = std::pair<T *, void(T:: *)()>>
|
||||||
explicit NativeThread(void(T::*fun)(), T* obj) {
|
explicit NativeThread(void(T:: *fun)(), T *obj)
|
||||||
pthread_attr_t attr_storage, *attr = &attr_storage;
|
{
|
||||||
pthread_attr_init(attr);
|
pthread_attr_t attr_storage, *attr = &attr_storage;
|
||||||
pthread_attr_setstacksize(attr, TH_STACK_SIZE);
|
pthread_attr_init(attr);
|
||||||
pthread_create(&thread, attr, start_routine<T>, new P(obj, fun));
|
pthread_attr_setstacksize(attr, TH_STACK_SIZE);
|
||||||
}
|
pthread_create(&thread, attr, start_routine<T>, new P(obj, fun));
|
||||||
void join() { pthread_join(thread, NULL); }
|
}
|
||||||
|
void join()
|
||||||
|
{
|
||||||
|
pthread_join(thread, NULL);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#else // Default case: use STL classes
|
#else // Default case: use STL classes
|
||||||
|
|
|
@ -133,7 +133,7 @@ namespace {
|
||||||
else if (token == "infinite") limits.infinite = 1;
|
else if (token == "infinite") limits.infinite = 1;
|
||||||
else if (token == "ponder") ponderMode = true;
|
else if (token == "ponder") ponderMode = true;
|
||||||
|
|
||||||
//Threads.start_thinking(pos, states, limits, ponderMode); // TODO
|
Threads.start_thinking(&pos, states, limits, ponderMode); // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue