/*
Texel - A UCI chess engine.
Copyright (C) 2013-2014 Peter Ă–sterlund, peterosterlund2@gmail.com
This program 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.
This program 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.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* parallel.cpp
*
* Created on: Jul 9, 2013
* Author: petero
*/
#include "parallel.hpp"
#include "numa.hpp"
#include "search.hpp"
#include "tbprobe.hpp"
#include "textio.hpp"
#include "util/logger.hpp"
#include <cmath>
#include <cassert>
using namespace Logger;
U64 SplitPoint::nextSeqNo = 0;
// ----------------------------------------------------------------------------
WorkerThread::WorkerThread(int threadNo0, ParallelData& pd0,
TranspositionTable& tt0)
: threadNo(threadNo0), pd(pd0), tt(tt0),
pUseful(0.0) {
}
WorkerThread::~WorkerThread() {
assert(!thread);
}
void
WorkerThread::start() {
assert(!thread);
const int minProbeDepth = TBProbe::tbEnabled() ? UciParams::minProbeDepth->getIntPar() : 100;
thread = std::make_shared<std::thread>([this,minProbeDepth](){ mainLoop(minProbeDepth); });
}
void
WorkerThread::join() {
if (thread) {
thread->join();
thread.reset();
}
}
class ThreadStopHandler : public Search::StopHandler {
public:
ThreadStopHandler(WorkerThread& wt, ParallelData& pd,
const SplitPoint& sp, const SplitPointMove& spm,
const Search& sc, int initialAlpha,
S64 totalNodes, int myPrio);
/** Destructor. Report searched nodes to ParallelData object. */
~ThreadStopHandler();
ThreadStopHandler(const ThreadStopHandler&) = delete;
ThreadStopHandler& operator=(const ThreadStopHandler&) = delete;
bool shouldStop();
private:
/** Report searched nodes since last call to ParallelData object. */
void reportNodes(bool force);
const WorkerThread& wt;
ParallelData& pd;
const SplitPoint& sp;
const SplitPointMove& spMove;
const Search& sc;
int counter; // Counts number of calls to shouldStop
int nextProbCheck; // Next time test for SplitPoint switch should be performed
S64 lastReportedNodes;
S64 lastReportedTbHits;
int initialAlpha;
const S64 totalNodes;
const int myPrio;
};
ThreadStopHandler::ThreadStopHandler(WorkerThread& wt0, ParallelData& pd0,
const SplitPoint& sp0, const SplitPointMove& spm0,
const Search& sc0, int initialAlpha0,
S64 totalNodes0, int myPrio0)
: wt(wt0), pd(pd0), sp(sp0), spMove(spm0),
sc(sc0), counter(0), nextProbCheck(1),
lastReportedNodes(0), lastReportedTbHits(0),
initialAlpha(initialAlpha0), totalNodes(totalNodes0), myPrio(myPrio0) {
}
ThreadStopHandler::~ThreadStopHandler() {
reportNodes(true);
}
bool
ThreadStopHandler::shouldStop() {
if (pd.wq.isStopped() || spMove.isCanceled())
return true;
if (sp.getAlpha() != initialAlpha)
return true;
counter++;
if (counter >= nextProbCheck) {
nextProbCheck = counter + 1 + counter / 4;
int bestPrio = pd.wq.getBestPrio();
// log([&](std::ostream& os){os << "shouldStop, th:" << wt.getThreadNo() << " myP:" << myPrio << " bestP:" << bestPrio;});
if ((bestPrio > myPrio + 20) && (bestPrio >= (myPrio + (1000 - myPrio) * 0.25)) &&
(sp.owningThread() != wt.getThreadNo()))
return true;
reportNodes(false);
}
return false;
}
void
ThreadStopHandler::reportNodes(bool force) {
S64 totNodes = sc.getTotalNodesThisThread();
S64 nodes = totNodes - lastReportedNodes;
if (force || (nodes * 1024 > totalNodes)) {
lastReportedNodes = totNodes;
pd.addSearchedNodes(nodes);
S64 totTbHits = sc.getTbHitsThisThread();
S64 tbHits = totTbHits - lastReportedTbHits;
lastReportedTbHits = totTbHits;
pd.addTbHits(tbHits);
}
}
void
WorkerThread::mainLoop(int minProbeDepth) {
// log([&](std::ostream& os){os << "mainLoop, th:" << threadNo;});
Numa::instance().bindThread(threadNo);
if (!et)
et = Evaluate::getEvalHashTables();
if (!kt)
kt = std::make_shared<KillerTable>();
if (!ht)
ht = std::make_shared<History>();
TreeLogger logFile;
logFile.open("/home/petero/treelog.dmp", pd, threadNo);
// UtilizationTimer uTimer;
std::mutex m;
std::unique_lock<std::mutex> lock(m);
Position pos;
std::shared_ptr<SplitPoint> sp;
for (int iter = 0; ; iter++) {
const bool doTiming = (iter & 15) == 0;
int moveNo = -1;
// uTimer.setPUseful(-1);
const double t0 = doTiming ? currentTime() : -1;
int prio;
std::shared_ptr<SplitPoint> newSp = pd.wq.getWork(moveNo, pd, threadNo, prio, pUseful);
if (!newSp)
break;
const double tStart = doTiming ? currentTime() : -1;
const SplitPointMove& spMove = newSp->getSpMove(moveNo);
const int depth = spMove.getDepth();
if (depth < 0) { // Move skipped by forward pruning or legality check
pd.wq.moveFinished(newSp, moveNo, false, SearchConst::UNKNOWN_SCORE);
continue;
}
if (sp != newSp) {
sp = newSp;
*ht = sp->getHistory();
*kt = sp->getKillerTable();
}
Search::SearchTables st(tt, *kt, *ht, *et);
sp->getPos(pos, spMove.getMove());
std::vector<U64> posHashList;
int posHashListSize;
sp->getPosHashList(pos, posHashList, posHashListSize);
Search sc(pos, posHashList, posHashListSize, st, pd, sp, logFile);
const U64 rootNodeIdx = logFile.logPosition(pos, sp->owningThread(),
sp->getSearchTreeInfo().nodeIdx, moveNo);
sc.setThreadNo(threadNo);
sc.setMinProbeDepth(minProbeDepth);
const int alpha = sp->getAlpha();
const int beta = sp->getBeta();
const S64 nodes0 = pd.getNumSearchedNodes();
auto stopHandler(std::make_shared<ThreadStopHandler>(*this, pd, *sp, spMove,
sc, alpha, nodes0, prio));
sc.setStopHandler(stopHandler);
const int ply = sp->getPly();
const int lmr = spMove.getLMR();
const int captSquare = spMove.getRecaptureSquare();
const bool inCheck = spMove.getInCheck();
sc.setSearchTreeInfo(ply, sp->getSearchTreeInfo(), spMove.getMove(), moveNo, lmr, rootNodeIdx);
try {
// log([&](std::ostream& os){os << "th:" << threadNo << " seqNo:" << sp->getSeqNo() << " ply:" << ply
// << " c:" << sp->getCurrMoveNo() << " m:" << moveNo
// << " a:" << alpha << " b:" << beta
// << " d:" << depth/SearchConst::plyScale
// << " p:" << sp->getPMoveUseful(pd.fhInfo, moveNo) << " start";});
// uTimer.setPUseful(pUseful);
const bool smp = pd.numHelperThreads() > 1;
int score = -sc.negaScout(smp, true, -(alpha+1), -alpha, ply+1,
depth, captSquare, inCheck);
if (((lmr > 0) && (score > alpha)) ||
((score > alpha) && (score < beta))) {
sc.setSearchTreeInfo(ply, sp->getSearchTreeInfo(), spMove.getMove(), moveNo, 0, rootNodeIdx);
score = -sc.negaScout(smp, true, -beta, -alpha, ply+1,
depth + lmr, captSquare, inCheck);
}
// uTimer.setPUseful(0);
bool cancelRemaining = score >= beta;
// log([&](std::ostream& os){os << "th:" << threadNo << " seqNo:" << sp->getSeqNo() << " ply:" << ply
// << " c:" << sp->getCurrMoveNo() << " m:" << moveNo
// << " a:" << alpha << " b:" << beta << " s:" << score
// << " d:" << depth/SearchConst::plyScale << " n:" << sc.getTotalNodesThisThread();});
pd.wq.moveFinished(sp, moveNo, cancelRemaining, score);
} catch (const Search::StopSearch&) {
// log([&](std::ostream& os){os << "th:" << threadNo << " seqNo:" << sp->getSeqNo() << " m:" << moveNo
// << " aborted n:" << sc.getTotalNodesThisThread();});
if (!spMove.isCanceled() && !pd.wq.isStopped())
pd.wq.returnMove(sp, moveNo);
}
if (doTiming) {
const double tEnd = currentTime();
pd.npsInfo.addData(sp->getDepth(), sc.getTotalNodesThisThread(), tStart - t0, tEnd - tStart);
}
pUseful = 0.0;
}
// double tElapsed, tUseful, tSleep;
// uTimer.getStats(tElapsed, tUseful, tSleep);
// log([&](std::ostream& os){
// os << "~mainLoop, th:" << threadNo << " useful:" << tUseful / tElapsed
// << " sleep:" << tSleep / tElapsed;
// });
// log([&](std::ostream& os){os << "~mainLoop, th:" << threadNo;});
}
// ----------------------------------------------------------------------------
void
WorkQueue::setStopped(bool stop) {
Lock L(this);
stopped = stop;
if (stopped)
cv.notify_all();
}
void
WorkQueue::addWork(const std::shared_ptr<SplitPoint>& sp) {
Lock L(this);
// ScopedTimeSample sts { getAddWorkStat(sp->owningThread()) };
addWorkInternal(sp);
}
void
WorkQueue::tryAddWork(const std::shared_ptr<SplitPoint>& sp,
std::vector<std::shared_ptr<SplitPoint>>& pending) {
std::unique_lock<std::mutex> lock(mutex, std::defer_lock);
if (!lock.try_lock()) {
pending.push_back(sp);
return;
}
for (const auto& sp : pending)
addWorkInternal(sp);
pending.clear();
addWorkInternal(sp);
}
void
WorkQueue::addWorkInternal(const std::shared_ptr<SplitPoint>& sp) {
sp->setSeqNo();
std::shared_ptr<SplitPoint> parent = sp->getParent();
if (parent) {
if (parent->isCanceled())
sp->cancel();
else
parent->addChild(sp);
}
if (sp->hasUnFinishedMove()) {
sp->computeProbabilities(fhInfo, npsInfo);
insertInQueue(sp);
}
}
std::shared_ptr<SplitPoint>
WorkQueue::getWork(int& spMove, ParallelData& pd, int threadNo) {
int prio;
double pUseful;
return getWork(spMove, pd, threadNo, prio, pUseful);
}
std::shared_ptr<SplitPoint>
WorkQueue::getWork(int& spMove, ParallelData& pd, int threadNo, int& prio, double& pUseful) {
Lock L(this);
while (true) {
while (queue.empty() && !isStopped())
L.wait(cv);
// ScopedTimeSample sts { getGetWorkStat(threadNo) };
if (isStopped())
return nullptr;
std::shared_ptr<SplitPoint> ret = queue.front();
spMove = ret->getNextMove(pd.fhInfo);
if (spMove < 0) {
L.wait(cv);
continue;
}
// log([&](std::ostream& os){printSpTree(os, pd, threadNo, ret, spMove);});
prio = ret->getPrio();
updateProbabilities(ret);
pUseful = ret->getPMoveUseful(pd.fhInfo, spMove);
return ret;
}
}
void
WorkQueue::returnMove(const std::shared_ptr<SplitPoint>& sp, int moveNo) {
Lock L(this);
sp->returnMove(moveNo);
updateProbabilities(sp);
}
int
WorkQueue::setOwnerCurrMove(const std::shared_ptr<SplitPoint>& sp, int moveNo, int alpha) {
Lock L(this);
int score = sp->setOwnerCurrMove(moveNo, alpha);
updateProbabilities(sp);
return score;
}
void
WorkQueue::cancel(const std::shared_ptr<SplitPoint>& sp) {
Lock L(this);
cancelInternal(sp);
}
void
WorkQueue::moveFinished(const std::shared_ptr<SplitPoint>& sp, int moveNo,
bool cancelRemaining, int score) {
Lock L(this);
sp->moveFinished(moveNo, cancelRemaining, score);
updateProbabilities(sp);
}
double
WorkQueue::getBestProbability(std::shared_ptr<SplitPoint>& bestSp) const {
Lock L(this);
if (queue.empty())
return 0.0;
bestSp = queue.front();
return bestSp->getPNextMoveUseful();
}
int
WorkQueue::getBestPrio() const {
Lock L(this);
if (queue.empty())
return -1;
return queue.front()->getPrio();
}
double
WorkQueue::getBestProbability() const {
std::shared_ptr<SplitPoint> bestSp;
return getBestProbability(bestSp);
}
void
WorkQueue::updateProbabilities(const std::shared_ptr<SplitPoint>& sp) {
if (!sp->hasUnFinishedMove())
queue.remove(sp);
sp->computeProbabilities(fhInfo, npsInfo);
}
void
WorkQueue::cancelInternal(const std::shared_ptr<SplitPoint>& sp) {
sp->cancel();
queue.remove(sp);
for (const auto& wChild : sp->getChildren()) {
std::shared_ptr<SplitPoint> child = wChild.lock();
if (child)
cancelInternal(child);
}
}
static std::string toPercentStr(double p) {
std::stringstream ss;
int pc = (int)(p * 100);
if (pc == 100)
pc = 99;
ss << std::setfill('0') << std::setw(2) << pc;
return ss.str();
}
void
WorkQueue::printSpTree(std::ostream& os, const ParallelData& pd,
int threadNo, const std::shared_ptr<SplitPoint> selectedSp,
int selectedMove) {
const int numThreads = pd.numHelperThreads() + 1;
std::shared_ptr<SplitPoint> rootSp = queue.front();
assert(rootSp);
while (rootSp->getParent())
rootSp = rootSp->getParent();
std::vector<int> parentThreads(numThreads, -1);
std::vector<std::shared_ptr<SplitPoint>> leaves(numThreads, nullptr);
findLeaves(rootSp, parentThreads, leaves);
os << "th:" << threadNo << " m: " << selectedMove << ':'
<< TextIO::moveToUCIString(selectedSp->getSpMove(selectedMove).getMove())
<< " p:" << selectedSp->getPMoveUseful(pd.fhInfo, selectedMove)
<< std::endl;
for (int i = 0; i < numThreads; i++) {
std::vector<std::shared_ptr<SplitPoint>> thVec;
for (auto sp = leaves[i]; sp; sp = sp->getParent())
thVec.push_back(sp);
std::reverse(thVec.begin(), thVec.end());
os << "th " << i << ' ';
if (parentThreads[i] < 0)
os << '-';
else
os << parentThreads[i];
os << ' ' << toPercentStr(i == 0 ? 1 : pd.getHelperThread(i-1).getPUseful());
if (!thVec.empty()) {
for (const auto& sp : thVec)
if (sp->owningThread() == i) {
os << ' ' << std::setw(6) << sp->getSeqNo();
break;
}
}
for (const auto& sp : thVec) {
if (sp->owningThread() == i) {
int pMove = sp->getParentMoveNo();
os << ' ' << std::setw(2) << pMove << (sp == selectedSp ? '*' : ':');
if (pMove < 0)
os << "null";
else if (sp->getParent())
os << TextIO::moveToUCIString(sp->getParent()->getSpMove(pMove).getMove());
else
os << TextIO::moveToUCIString(pd.topMove);
os << ',' << toPercentStr(sp->getPSpUseful())
<< ':' << toPercentStr(sp->getPNextMoveUseful());
os << ',' << std::setw(2) << sp->getCurrMoveNo() << ':' << std::setw(2) << sp->findNextMove(pd.fhInfo);
} else {
os << " ";
}
}
os << std::endl;
}
}
void
WorkQueue::findLeaves(const std::shared_ptr<SplitPoint>& sp,
std::vector<int>& parentThreads,
std::vector<std::shared_ptr<SplitPoint>>& leaves) {
bool isLeaf = true;
const std::vector<std::weak_ptr<SplitPoint>>& children = sp->getChildren();
for (const auto& wChild : children) {
std::shared_ptr<SplitPoint> child = wChild.lock();
if (child && !child->isCanceled()) {
if (child->owningThread() == sp->owningThread()) {
isLeaf = false;
} else {
assert(parentThreads[child->owningThread()] == -1);
parentThreads[child->owningThread()] = sp->owningThread();
}
findLeaves(child, parentThreads, leaves);
}
}
if (isLeaf) {
assert(sp->owningThread() >= 0);
assert(sp->owningThread() < (int)leaves.size());
leaves[sp->owningThread()] = sp;
}
}
WorkQueue::Lock::Lock(const WorkQueue* wq0)
: wq(*wq0), lock(wq.mutex, std::defer_lock) {
bool contended = false;
if (!lock.try_lock()) {
contended = true;
lock.lock();
}
if (wq.queue.empty())
contended = false;
U64 c = wq.nContended;
U64 n = wq.nNonContended;
if (contended)
c++;
else
n++;
if (n + c > 30000) {
c /= 2;
n /= 2;
if (c * 100 > n * 50) {
wq.minSplitDepth++;
// std::cout << "contended stat: " << wq.minSplitDepth << " " << c << " " << n << std::endl;
} else if ((c * 100 < n * 25) && (wq.minSplitDepth > SearchConst::MIN_SMP_DEPTH)) {
wq.minSplitDepth--;
// std::cout << "contended stat: " << wq.minSplitDepth << " " << c << " " << n << std::endl;
}
}
wq.nContended = c;
wq.nNonContended = n;
}
// ----------------------------------------------------------------------------
void
ParallelData::addRemoveWorkers(int numWorkers) {
while (numWorkers < (int)threads.size()) {
assert(!threads.back()->threadRunning());
threads.pop_back();
}
for (int i = threads.size(); i < numWorkers; i++)
threads.push_back(std::make_shared<WorkerThread>(i+1, *this, tt));
}
void
ParallelData::startAll() {
totalHelperNodes = 0;
helperTbHits = 0;
wq.setStopped(false);
for (auto& thread : threads)
thread->start();
}
void
ParallelData::stopAll() {
wq.setStopped(true);
for (auto& thread : threads)
thread->join();
}
// ----------------------------------------------------------------------------
SplitPoint::SplitPoint(int threadNo0,
const std::shared_ptr<SplitPoint>& parentSp0, int parentMoveNo0,
const Position& pos0, const std::vector<U64>& posHashList0,
int posHashListSize0, const SearchTreeInfo& sti0,
const KillerTable& kt0, const History& ht0,
int alpha0, int beta0, int ply0, int depth0)
: pos(pos0), posHashList(posHashList0), posHashListSize(posHashListSize0),
searchTreeInfo(sti0), kt(kt0), ht(ht0),
alpha(alpha0), beta(beta0), ply(ply0), depth(depth0),
isPV(beta0 > alpha0 + 1),
pSpUseful(0.0), pNextMoveUseful(0.0),
threadNo(threadNo0), parent(parentSp0), parentMoveNo(parentMoveNo0),
seqNo(0), currMoveNo(0), inserted(false), canceled(false) {
}
void
SplitPoint::addMove(int moveNo, const SplitPointMove& spMove) {
assert(moveNo >= (int)spMoves.size());
while ((int)spMoves.size() < moveNo)
spMoves.push_back(SplitPointMove(Move(), 0, -1, -1, false));
spMoves.push_back(spMove);
}
void
SplitPoint::computeProbabilities(const FailHighInfo& fhInfo, const DepthNpsInfo& npsInfo) {
if (parent) {
double pMoveUseful = 1.0;
if (parentMoveNo >= 0)
pMoveUseful = parent->getMoveNeededProbability(fhInfo, parentMoveNo);
pSpUseful = parent->pSpUseful * pMoveUseful;
} else {
pSpUseful = 1.0;
}
double pNextUseful = getMoveNeededProbability(fhInfo, findNextMove(fhInfo));
pNextMoveUseful = pSpUseful * pNextUseful;
newPrio(getSpPrio(npsInfo));
bool deleted = false;
for (const auto& wChild : children) {
std::shared_ptr<SplitPoint> child = wChild.lock();
if (child)
child->computeProbabilities(fhInfo, npsInfo);
else
deleted = true;
}
if (deleted)
cleanUpChildren();
}
double
SplitPoint::getPMoveUseful(const FailHighInfo& fhInfo, int moveNo) const {
return pSpUseful * getMoveNeededProbability(fhInfo, moveNo);
}
void
SplitPoint::getPos(Position& pos, const Move& move) const {
pos = this->pos;
UndoInfo ui;
pos.makeMove(move, ui);
}
void
SplitPoint::getPosHashList(const Position& pos, std::vector<U64>& posHashList,
int& posHashListSize) const {
posHashList = this->posHashList;
posHashListSize = this->posHashListSize;
posHashList[posHashListSize++] = pos.zobristHash();
}
int
SplitPoint::getNextMove(const FailHighInfo& fhInfo) {
int m = findNextMove(fhInfo);
if (m < 0)
return m;
spMoves[m].setSearching(true);
return m;
}
void
SplitPoint::moveFinished(int moveNo, bool cancelRemaining, int score) {
assert((moveNo >= 0) && (moveNo < (int)spMoves.size()));
spMoves[moveNo].setScore(score);
spMoves[moveNo].setSearching(false);
spMoves[moveNo].setCanceled(true);
if (cancelRemaining)
for (int i = moveNo+1; i < (int)spMoves.size(); i++)
spMoves[i].setCanceled(true);
}
bool
SplitPoint::hasUnStartedMove() const {
if (canceled)
return false;
for (int i = currMoveNo + 1; i < (int)spMoves.size(); i++)
if (!spMoves[i].isCanceled() && !spMoves[i].isSearching())
return true;
return false;
}
bool
SplitPoint::hasUnFinishedMove() const {
if (canceled)
return false;
for (int i = currMoveNo + 1; i < (int)spMoves.size(); i++)
if (!spMoves[i].isCanceled())
return true;
return false;
}
int
SplitPoint::findNextMove(const FailHighInfo& fhInfo) const {
int i0 = -1;
const double pGood = 0.98;
for (int i = currMoveNo+1; i < (int)spMoves.size(); i++)
if (!spMoves[i].isCanceled() && !spMoves[i].isSearching()) {
if ((getPNextMoveUseful() > pGood) && (i0 == -1))
i0 = i;
else {
if ((i0 != -1) && (getPMoveUseful(fhInfo, i) <= pGood))
return i0;
return i;
}
}
return i0;
}
double
SplitPoint::getMoveNeededProbability(const FailHighInfo& fhInfo, int moveNo) const {
if (isPvNode())
return fhInfo.getMoveNeededProbabilityPv(currMoveNo, moveNo);
else
return fhInfo.getMoveNeededProbability(parentMoveNo,
currMoveNo,
moveNo, isAllNode());
}
void
SplitPoint::cleanUpChildren() {
std::vector<std::weak_ptr<SplitPoint>> toKeep;
for (const auto& wChild : children)
if (wChild.lock())
toKeep.push_back(wChild);
children = toKeep;
}
bool
SplitPoint::hasHelperThread() const {
for (const auto& wChild : children) {
std::shared_ptr<SplitPoint> child = wChild.lock();
if (child && child->owningThread() != owningThread())
return true;
}
return false;
}
bool
SplitPoint::isAncestorTo(const SplitPoint& sp) const {
const SplitPoint* tmp = &sp;
while (tmp) {
if (tmp == this)
return true;
tmp = &*(tmp->parent);
}
return false;
}
bool
SplitPoint::isAllNode() const {
int nFirst = 0;
const SplitPoint* tmp = this;
while (tmp) {
if (tmp->parentMoveNo == 0)
nFirst++;
else
break;
tmp = &*(tmp->parent);
}
return (nFirst % 2) != 0;
}
void
SplitPoint::print(std::ostream& os, int level, const FailHighInfo& fhInfo) const {
std::string pad(level*2, ' ');
os << pad << "seq:" << seqNo << " pos:" << TextIO::toFEN(pos) << std::endl;
os << pad << "parent:" << parentMoveNo << " hashListSize:" << posHashListSize <<
" a:" << alpha << " b:" << beta << " ply:" << ply << " canceled:" << canceled << std::endl;
os << pad << "p1:" << pSpUseful << " p2:" << pNextMoveUseful << " curr:" << currMoveNo << std::endl;
os << pad << "moves:";
for (int mi = 0; mi < (int)spMoves.size(); mi++) {
const auto& spm = spMoves[mi];
os << ' ' << TextIO::moveToUCIString(spm.getMove());
if (spm.isCanceled())
os << ",c";
if (spm.isSearching())
os << ",s";
os << "," << getMoveNeededProbability(fhInfo, mi);
}
os << std::endl;
for (const auto& wChild : children) {
std::shared_ptr<SplitPoint> child = wChild.lock();
if (child)
child->print(os, level+1, fhInfo);
}
}
// ----------------------------------------------------------------------------
void
SplitPointHolder::setSp(const std::shared_ptr<SplitPoint>& sp0) {
assert(state == State::EMPTY);
assert(sp0);
sp = sp0;
if (sp->getBeta() > sp->getAlpha() + 1)
pd.fhInfo.addPvData(-1, false);
state = State::CREATED;
}
void
SplitPointHolder::addToQueue() {
assert(state == State::CREATED);
pd.wq.tryAddWork(sp, pending);
spVec.push_back(sp);
// log([&](std::ostream& os){os << "add seqNo:" << sp->getSeqNo() << " ply:" << sp->getPly()
// << " pNext:" << sp->getPNextMoveUseful()
// << " pMove:" << sp->getParentMoveNo() << " vec:" << spVec.size();});
state = State::QUEUED;
}
// ----------------------------------------------------------------------------
FailHighInfo::FailHighInfo()
: totCount(0) {
for (int i = 0; i < NUM_NODE_TYPES; i++)
failLoCount[i] = 0;
for (int j = 0; j < NUM_STAT_MOVES; j++)
newAlpha[j] = 0;
totPvCount = 0;
}
double
FailHighInfo::getMoveNeededProbability(int parentMoveNo,
int currMoveNo, int moveNo, bool allNode) const {
const int pIdx = getNodeType(parentMoveNo, allNode);
moveNo = std::min(moveNo, NUM_STAT_MOVES-1);
if (moveNo < 0)
return 0.0;
int nNeeded = failLoCount[pIdx] + failHiCount[pIdx].sum(moveNo, NUM_STAT_MOVES);
int nTotal = nNeeded + failHiCount[pIdx].sum(currMoveNo, moveNo);
return (nTotal > 0) ? nNeeded / (double)nTotal : 0.5;
}
double
FailHighInfo::getMoveNeededProbabilityPv(int currMoveNo, int moveNo) const {
moveNo = std::min(moveNo, NUM_STAT_MOVES-1);
if (moveNo < 0)
return 0.0;
if (totPvCount <= 0)
return 0.5;
double prob = 1.0;
double inv = 1.0 / totPvCount;
for (int i = currMoveNo; i < moveNo; i++)
prob *= std::max(0.0, 1.0 - newAlpha[i] * inv);
return prob;
}
void
FailHighInfo::addData(int parentMoveNo, int nSearched, bool failHigh, bool allNode) {
if (nSearched < 0)
return;
const int pIdx = getNodeType(parentMoveNo, allNode);
if (failHigh) {
nSearched = std::min(nSearched, NUM_STAT_MOVES-1);
failHiCount[pIdx].add(nSearched, 1);
} else {
failLoCount[pIdx]++;
}
totCount++;
if (totCount >= 1000000) {
std::lock_guard<std::mutex> L(mutex);
if (totCount >= 1000000)
reScaleInternal(2);
}
}
void
FailHighInfo::addPvData(int nSearched, bool alphaChanged) {
if (nSearched >= 0) {
if (alphaChanged && nSearched < NUM_STAT_MOVES)
newAlpha[nSearched]++;
} else {
totPvCount++;
if (totPvCount >= 10000) {
std::lock_guard<std::mutex> L(mutex);
if (totPvCount >= 10000)
reScalePv(2);
}
}
}
void
FailHighInfo::reScale() {
reScaleInternal(4);
reScalePv(4);
}
void
FailHighInfo::reScaleInternal(int factor) {
for (int i = 0; i < NUM_NODE_TYPES; i++) {
for (int j = 0; j < NUM_STAT_MOVES; j++) {
int val = failHiCount[i].get(j);
failHiCount[i].add(j, val / factor - val);
}
failLoCount[i] = failLoCount[i] / factor;
}
totCount = totCount / factor;
}
void
FailHighInfo::reScalePv(int factor) {
for (int j = 0; j < NUM_STAT_MOVES; j++)
newAlpha[j] = newAlpha[j] / factor;
totPvCount = totPvCount / factor;
}
void
FailHighInfo::print(std::ostream& os) const {
for (int i = 0; i < NUM_NODE_TYPES; i++) {
os << "fhInfo: " << i << ' ' << std::setw(6) << failLoCount[i];
for (int j = 0; j < NUM_STAT_MOVES; j++)
os << ' ' << std::setw(6) << failHiCount[i].get(j);
os << std::endl;
}
os << "fhInfo: " << NUM_NODE_TYPES << ' ' << std::setw(6) << totPvCount;
for (int j = 0; j < NUM_STAT_MOVES; j++)
os << ' ' << std::setw(6) << newAlpha[j];
os << std::endl;
}
// ----------------------------------------------------------------------------
void
DepthNpsInfo::print(std::ostream& os, int iterDepth) {
std::lock_guard<std::mutex> L(mutex);
os << "npsInfo: depth:" << iterDepth << " nps0:" << nps0
<< " wait:" << waitTime / nSearches << std::endl;
for (int i = SearchConst::MIN_SMP_DEPTH; i < maxDepth; i++) {
os << "npsInfo: d:" << i
<< " n:" << npsData[i].nSearches
<< " nodes:" << npsData[i].nodes
<< " time:" << npsData[i].time
<< " nps:" << npsData[i].nodes / npsData[i].time
<< " ts:" << npsData[i].nodes / (double)npsData[i].nSearches
<< " eff:" << efficiencyInternal(i) << std::endl;
}
}