从 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";
|
||||
#endif
|
||||
t = t;
|
||||
return os;
|
||||
}
|
||||
}
|
||||
|
@ -213,5 +214,6 @@ std::string Eval::trace(Position *pos)
|
|||
|
||||
return ss.str();
|
||||
#endif
|
||||
pos = pos;
|
||||
return "";
|
||||
}
|
|
@ -134,6 +134,7 @@ namespace CTSL //Concurrent Thread Safe Library
|
|||
void resize(size_t o)
|
||||
{
|
||||
// TODO
|
||||
o = o;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -139,6 +139,10 @@ Position &Position::set(const string &fenStr, StateInfo *si, Thread *th)
|
|||
|
||||
assert(pos_is_ok());
|
||||
#endif
|
||||
th = th;
|
||||
si = si;
|
||||
string str = fenStr;
|
||||
str = "";
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -162,6 +166,7 @@ void Position::set_state(StateInfo *si) const
|
|||
if (sideToMove == BLACK)
|
||||
si->key ^= Zobrist::side;
|
||||
#endif
|
||||
si = si;
|
||||
}
|
||||
|
||||
|
||||
|
@ -188,6 +193,10 @@ Position &Position::set(const string &code, Color c, StateInfo *si)
|
|||
|
||||
return set(fenStr, si, nullptr);
|
||||
#endif
|
||||
si = si;
|
||||
c = c;
|
||||
string ccc = code;
|
||||
ccc = "";
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#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 <chrono>
|
||||
#include <algorithm>
|
||||
|
||||
#include "search.h"
|
||||
#include "evaluate.h"
|
||||
#include "hashmap.h"
|
||||
#include "tt.h"
|
||||
#include "endgame.h"
|
||||
#include "types.h"
|
||||
#include "option.h"
|
||||
#include "misc.h"
|
||||
#include "movepick.h"
|
||||
|
||||
using namespace CTSL;
|
||||
|
||||
|
@ -39,6 +50,8 @@ namespace Search
|
|||
LimitsType Limits;
|
||||
}
|
||||
|
||||
using namespace Search;
|
||||
|
||||
vector<Key> moveHistory;
|
||||
|
||||
AIAlgorithm::AIAlgorithm()
|
||||
|
@ -738,4 +751,146 @@ void Search::clear()
|
|||
{
|
||||
// TODO
|
||||
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
|
||||
{
|
||||
/// 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
|
||||
/// 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) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
||||
Copyright (C) 2015-2019 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
||||
Copyright (C) 2019-2020 Calcitem <calcitem@outlook.com>
|
||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish author)
|
||||
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
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(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
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
@ -25,6 +25,7 @@
|
|||
#include "movegen.h"
|
||||
#include "search.h"
|
||||
#include "thread.h"
|
||||
#include "uci.h"
|
||||
#include "tt.h"
|
||||
|
||||
ThreadPool Threads; // Global object
|
||||
|
@ -33,161 +34,184 @@ ThreadPool Threads; // Global object
|
|||
/// Thread constructor launches the thread and waits until it goes to sleep
|
||||
/// 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
|
||||
/// for its termination. Thread should be already waiting.
|
||||
|
||||
Thread::~Thread() {
|
||||
Thread::~Thread()
|
||||
{
|
||||
|
||||
assert(!searching);
|
||||
assert(!searching);
|
||||
|
||||
exit = true;
|
||||
start_searching();
|
||||
stdThread.join();
|
||||
exit = true;
|
||||
start_searching();
|
||||
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
|
||||
|
||||
void Thread::clear() {
|
||||
|
||||
void Thread::clear()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
/// 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);
|
||||
searching = true;
|
||||
cv.notify_one(); // Wake up the thread in idle_loop()
|
||||
std::lock_guard<std::mutex> lk(mutex);
|
||||
searching = true;
|
||||
cv.notify_one(); // Wake up the thread in idle_loop()
|
||||
}
|
||||
|
||||
|
||||
/// Thread::wait_for_search_finished() blocks on the condition variable
|
||||
/// until the thread has finished searching.
|
||||
|
||||
void Thread::wait_for_search_finished() {
|
||||
void Thread::wait_for_search_finished()
|
||||
{
|
||||
|
||||
std::unique_lock<Mutex> lk(mutex);
|
||||
cv.wait(lk, [&]{ return !searching; });
|
||||
std::unique_lock<std::mutex> lk(mutex);
|
||||
cv.wait(lk, [&] { return !searching; });
|
||||
}
|
||||
|
||||
|
||||
/// Thread::idle_loop() is where the thread is parked, blocked on the
|
||||
/// 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
|
||||
// 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,
|
||||
// just check if running threads are below a threshold, in this case all this
|
||||
// NUMA machinery is not needed.
|
||||
//if (Options["Threads"] > 8)
|
||||
// WinProcGroup::bindThisThread(idx);
|
||||
// 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
|
||||
// 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
|
||||
// NUMA machinery is not needed.
|
||||
if (Options["Threads"] > 8)
|
||||
WinProcGroup::bindThisThread(idx);
|
||||
|
||||
while (true)
|
||||
{
|
||||
std::unique_lock<Mutex> lk(mutex);
|
||||
searching = false;
|
||||
cv.notify_one(); // Wake up anyone waiting for search finished
|
||||
cv.wait(lk, [&]{ return searching; });
|
||||
while (true) {
|
||||
std::unique_lock<std::mutex> lk(mutex);
|
||||
searching = false;
|
||||
cv.notify_one(); // Wake up anyone waiting for search finished
|
||||
cv.wait(lk, [&] { return searching; });
|
||||
|
||||
if (exit)
|
||||
return;
|
||||
if (exit)
|
||||
return;
|
||||
|
||||
lk.unlock();
|
||||
lk.unlock();
|
||||
|
||||
//search();
|
||||
}
|
||||
search();
|
||||
}
|
||||
}
|
||||
|
||||
/// ThreadPool::set() creates/destroys threads to match the requested number.
|
||||
/// Created and launched threads will immediately go to sleep in idle_loop.
|
||||
/// 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)
|
||||
main()->wait_for_search_finished();
|
||||
if (size() > 0) { // destroy any existing thread(s)
|
||||
main()->wait_for_search_finished();
|
||||
|
||||
while (size() > 0)
|
||||
delete back(), pop_back();
|
||||
}
|
||||
while (size() > 0)
|
||||
delete back(), pop_back();
|
||||
}
|
||||
|
||||
if (requested > 0) { // create new thread(s)
|
||||
push_back(new MainThread(0));
|
||||
if (requested > 0) { // create new thread(s)
|
||||
push_back(new MainThread(0));
|
||||
|
||||
while (size() < requested)
|
||||
push_back(new Thread(size()));
|
||||
clear();
|
||||
while (size() < requested)
|
||||
push_back(new Thread(size()));
|
||||
clear();
|
||||
|
||||
// Reallocate the key with the new threadpool size
|
||||
//TT.resize(Options["Hash"]);
|
||||
}
|
||||
// Reallocate the hash with the new threadpool size
|
||||
TT.resize((size_t)Options["Hash"]);
|
||||
|
||||
// Init thread number dependent search params.
|
||||
Search::init();
|
||||
}
|
||||
}
|
||||
|
||||
/// ThreadPool::clear() sets threadPool data to initial values.
|
||||
|
||||
void ThreadPool::clear() {
|
||||
void ThreadPool::clear()
|
||||
{
|
||||
|
||||
for (Thread* th : *this)
|
||||
th->clear();
|
||||
for (Thread *th : *this)
|
||||
th->clear();
|
||||
|
||||
main()->callsCnt = 0;
|
||||
main()->previousScore = VALUE_INFINITE;
|
||||
main()->previousTimeReduction = 1.0;
|
||||
main()->callsCnt = 0;
|
||||
main()->bestPreviousScore = VALUE_INFINITE;
|
||||
main()->previousTimeReduction = 1.0;
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#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;
|
||||
main()->ponder = ponderMode;
|
||||
Search::Limits = limits;
|
||||
Search::RootMoves rootMoves;
|
||||
void ThreadPool::start_thinking(Position *pos, StateListPtr &states,
|
||||
const Search::LimitsType &limits, bool ponderMode)
|
||||
{
|
||||
|
||||
for (const auto& m : MoveList<LEGAL>(pos))
|
||||
if ( limits.searchmoves.empty()
|
||||
|| std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m))
|
||||
rootMoves.emplace_back(m);
|
||||
main()->wait_for_search_finished();
|
||||
|
||||
if (!rootMoves.empty())
|
||||
Tablebases::rank_root_moves(pos, rootMoves);
|
||||
main()->stopOnPonderhit = stop = false;
|
||||
increaseDepth = true;
|
||||
main()->ponder = ponderMode;
|
||||
Search::Limits = limits;
|
||||
Search::RootMoves rootMoves;
|
||||
|
||||
// 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());
|
||||
for (const auto &m : MoveList(pos))
|
||||
if (limits.searchmoves.empty()
|
||||
|| std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m))
|
||||
rootMoves.emplace_back(m);
|
||||
|
||||
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->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();
|
||||
}
|
||||
#ifdef TBPROBE
|
||||
if (!rootMoves.empty())
|
||||
Tablebases::rank_root_moves(pos, rootMoves);
|
||||
#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) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
||||
Copyright (C) 2015-2019 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
||||
Copyright (C) 2019-2020 Calcitem <calcitem@outlook.com>
|
||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish author)
|
||||
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
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(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
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
@ -28,58 +28,64 @@
|
|||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "movepick.h"
|
||||
#include "position.h"
|
||||
#include "search.h"
|
||||
#include "thread_win32_osx.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
class Position;
|
||||
|
||||
/// 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
|
||||
/// to care about someone changing the entry under our feet.
|
||||
|
||||
class Thread {
|
||||
|
||||
Mutex mutex;
|
||||
ConditionVariable cv;
|
||||
size_t idx;
|
||||
bool exit = false, searching = true; // Set before starting std::thread
|
||||
NativeThread stdThread;
|
||||
class Thread
|
||||
{
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
size_t idx;
|
||||
bool exit = false, searching = true; // Set before starting std::thread
|
||||
NativeThread stdThread;
|
||||
|
||||
public:
|
||||
explicit Thread(size_t);
|
||||
virtual ~Thread();
|
||||
//virtual void search();
|
||||
void clear();
|
||||
void idle_loop();
|
||||
void start_searching();
|
||||
void wait_for_search_finished();
|
||||
explicit Thread(size_t);
|
||||
virtual ~Thread();
|
||||
virtual void search();
|
||||
void clear();
|
||||
void idle_loop();
|
||||
void start_searching();
|
||||
void wait_for_search_finished();
|
||||
int best_move_count(Move move) const;
|
||||
|
||||
size_t pvIdx, multiPV, pvLast, shuffleExts;
|
||||
int selDepth, nmpMinPly;
|
||||
std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
|
||||
size_t pvIdx, pvLast;
|
||||
uint64_t ttHitAverage;
|
||||
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
|
||||
|
||||
struct MainThread : public Thread {
|
||||
struct MainThread : public Thread
|
||||
{
|
||||
|
||||
using Thread::Thread;
|
||||
using Thread::Thread;
|
||||
|
||||
//void search() override;
|
||||
//void check_time();
|
||||
void search() override;
|
||||
void check_time();
|
||||
|
||||
double previousTimeReduction;
|
||||
Value previousScore;
|
||||
int callsCnt;
|
||||
bool stopOnPonderhit;
|
||||
std::atomic_bool ponder;
|
||||
double previousTimeReduction;
|
||||
Value bestPreviousScore;
|
||||
Value iterValue[4];
|
||||
int callsCnt;
|
||||
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
|
||||
/// 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 clear();
|
||||
void set(size_t);
|
||||
void start_thinking(Position *, StateListPtr &, const Search::LimitsType &, bool = false);
|
||||
void clear();
|
||||
void set(size_t);
|
||||
|
||||
MainThread* main() const { return static_cast<MainThread*>(front()); }
|
||||
uint64_t nodes_searched() const { return accumulate(&Thread::nodes); }
|
||||
uint64_t tb_hits() const { return accumulate(&Thread::tbHits); }
|
||||
MainThread *main() const
|
||||
{
|
||||
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:
|
||||
//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;
|
||||
for (Thread* th : *this)
|
||||
sum += (th->*member).load(std::memory_order_relaxed);
|
||||
return sum;
|
||||
}
|
||||
uint64_t sum = 0;
|
||||
for (Thread *th : *this)
|
||||
sum += (th->*member).load(std::memory_order_relaxed);
|
||||
return sum;
|
||||
}
|
||||
};
|
||||
|
||||
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) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
||||
Copyright (C) 2015-2019 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish author)
|
||||
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
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(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
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
@ -21,86 +22,47 @@
|
|||
#ifndef 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>
|
||||
|
||||
#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
|
||||
/// size of 512KB by default, this is dangerously low for deep searches, so
|
||||
/// adjust it to TH_STACK_SIZE. The implementation calls pthread_create() with
|
||||
/// proper stack size parameter.
|
||||
/// size of 512KB by default, this is too low for deep searches, which require
|
||||
/// somewhat more than 1MB stack, so adjust it to TH_STACK_SIZE.
|
||||
/// 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>
|
||||
|
||||
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::*)()>>
|
||||
void* start_routine(void* ptr)
|
||||
template <class T, class P = std::pair<T *, void(T:: *)()>>
|
||||
void *start_routine(void *ptr)
|
||||
{
|
||||
P* p = reinterpret_cast<P*>(ptr);
|
||||
(p->first->*(p->second))(); // Call member function pointer
|
||||
delete p;
|
||||
return NULL;
|
||||
P *p = reinterpret_cast<P *>(ptr);
|
||||
(p->first->*(p->second))(); // Call member function pointer
|
||||
delete p;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
class NativeThread {
|
||||
class NativeThread
|
||||
{
|
||||
|
||||
pthread_t thread;
|
||||
pthread_t thread;
|
||||
|
||||
public:
|
||||
template<class T, class P = std::pair<T*, void(T::*)()>>
|
||||
explicit NativeThread(void(T::*fun)(), T* obj) {
|
||||
pthread_attr_t attr_storage, *attr = &attr_storage;
|
||||
pthread_attr_init(attr);
|
||||
pthread_attr_setstacksize(attr, TH_STACK_SIZE);
|
||||
pthread_create(&thread, attr, start_routine<T>, new P(obj, fun));
|
||||
}
|
||||
void join() { pthread_join(thread, NULL); }
|
||||
template<class T, class P = std::pair<T *, void(T:: *)()>>
|
||||
explicit NativeThread(void(T:: *fun)(), T *obj)
|
||||
{
|
||||
pthread_attr_t attr_storage, *attr = &attr_storage;
|
||||
pthread_attr_init(attr);
|
||||
pthread_attr_setstacksize(attr, TH_STACK_SIZE);
|
||||
pthread_create(&thread, attr, start_routine<T>, new P(obj, fun));
|
||||
}
|
||||
void join()
|
||||
{
|
||||
pthread_join(thread, NULL);
|
||||
}
|
||||
};
|
||||
|
||||
#else // Default case: use STL classes
|
||||
|
|
|
@ -133,7 +133,7 @@ namespace {
|
|||
else if (token == "infinite") limits.infinite = 1;
|
||||
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