Details | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 99 | pmbaty | 1 | /* |
| 2 | Texel - A UCI chess engine. |
||
| 3 | Copyright (C) 2013-2014 Peter Ă–sterlund, peterosterlund2@gmail.com |
||
| 4 | |||
| 5 | This program is free software: you can redistribute it and/or modify |
||
| 6 | it under the terms of the GNU General Public License as published by |
||
| 7 | the Free Software Foundation, either version 3 of the License, or |
||
| 8 | (at your option) any later version. |
||
| 9 | |||
| 10 | This program is distributed in the hope that it will be useful, |
||
| 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
| 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
| 13 | GNU General Public License for more details. |
||
| 14 | |||
| 15 | You should have received a copy of the GNU General Public License |
||
| 16 | along with this program. If not, see <http://www.gnu.org/licenses/>. |
||
| 17 | */ |
||
| 18 | |||
| 19 | /* |
||
| 20 | * parallel.cpp |
||
| 21 | * |
||
| 22 | * Created on: Jul 9, 2013 |
||
| 23 | * Author: petero |
||
| 24 | */ |
||
| 25 | |||
| 26 | #include "parallel.hpp" |
||
| 27 | #include "numa.hpp" |
||
| 28 | #include "search.hpp" |
||
| 29 | #include "tbprobe.hpp" |
||
| 30 | #include "textio.hpp" |
||
| 31 | #include "util/logger.hpp" |
||
| 32 | |||
| 33 | #include <cmath> |
||
| 34 | #include <cassert> |
||
| 35 | |||
| 36 | using namespace Logger; |
||
| 37 | |||
| 38 | U64 SplitPoint::nextSeqNo = 0; |
||
| 39 | |||
| 40 | // ---------------------------------------------------------------------------- |
||
| 41 | |||
| 42 | WorkerThread::WorkerThread(int threadNo0, ParallelData& pd0, |
||
| 43 | TranspositionTable& tt0) |
||
| 44 | : threadNo(threadNo0), pd(pd0), tt(tt0), |
||
| 45 | pUseful(0.0) { |
||
| 46 | } |
||
| 47 | |||
| 48 | WorkerThread::~WorkerThread() { |
||
| 49 | assert(!thread); |
||
| 50 | } |
||
| 51 | |||
| 52 | void |
||
| 53 | WorkerThread::start() { |
||
| 54 | assert(!thread); |
||
| 55 | const int minProbeDepth = TBProbe::tbEnabled() ? UciParams::minProbeDepth->getIntPar() : 100; |
||
| 56 | thread = std::make_shared<std::thread>([this,minProbeDepth](){ mainLoop(minProbeDepth); }); |
||
| 57 | } |
||
| 58 | |||
| 59 | void |
||
| 60 | WorkerThread::join() { |
||
| 61 | if (thread) { |
||
| 62 | thread->join(); |
||
| 63 | thread.reset(); |
||
| 64 | } |
||
| 65 | } |
||
| 66 | |||
| 67 | class ThreadStopHandler : public Search::StopHandler { |
||
| 68 | public: |
||
| 69 | ThreadStopHandler(WorkerThread& wt, ParallelData& pd, |
||
| 70 | const SplitPoint& sp, const SplitPointMove& spm, |
||
| 71 | const Search& sc, int initialAlpha, |
||
| 72 | S64 totalNodes, int myPrio); |
||
| 73 | |||
| 74 | /** Destructor. Report searched nodes to ParallelData object. */ |
||
| 75 | ~ThreadStopHandler(); |
||
| 76 | |||
| 77 | ThreadStopHandler(const ThreadStopHandler&) = delete; |
||
| 78 | ThreadStopHandler& operator=(const ThreadStopHandler&) = delete; |
||
| 79 | |||
| 80 | bool shouldStop(); |
||
| 81 | |||
| 82 | private: |
||
| 83 | /** Report searched nodes since last call to ParallelData object. */ |
||
| 84 | void reportNodes(bool force); |
||
| 85 | |||
| 86 | const WorkerThread& wt; |
||
| 87 | ParallelData& pd; |
||
| 88 | const SplitPoint& sp; |
||
| 89 | const SplitPointMove& spMove; |
||
| 90 | const Search& sc; |
||
| 91 | int counter; // Counts number of calls to shouldStop |
||
| 92 | int nextProbCheck; // Next time test for SplitPoint switch should be performed |
||
| 93 | S64 lastReportedNodes; |
||
| 94 | S64 lastReportedTbHits; |
||
| 95 | int initialAlpha; |
||
| 96 | const S64 totalNodes; |
||
| 97 | const int myPrio; |
||
| 98 | }; |
||
| 99 | |||
| 100 | ThreadStopHandler::ThreadStopHandler(WorkerThread& wt0, ParallelData& pd0, |
||
| 101 | const SplitPoint& sp0, const SplitPointMove& spm0, |
||
| 102 | const Search& sc0, int initialAlpha0, |
||
| 103 | S64 totalNodes0, int myPrio0) |
||
| 104 | : wt(wt0), pd(pd0), sp(sp0), spMove(spm0), |
||
| 105 | sc(sc0), counter(0), nextProbCheck(1), |
||
| 106 | lastReportedNodes(0), lastReportedTbHits(0), |
||
| 107 | initialAlpha(initialAlpha0), totalNodes(totalNodes0), myPrio(myPrio0) { |
||
| 108 | } |
||
| 109 | |||
| 110 | ThreadStopHandler::~ThreadStopHandler() { |
||
| 111 | reportNodes(true); |
||
| 112 | } |
||
| 113 | |||
| 114 | bool |
||
| 115 | ThreadStopHandler::shouldStop() { |
||
| 116 | if (pd.wq.isStopped() || spMove.isCanceled()) |
||
| 117 | return true; |
||
| 118 | if (sp.getAlpha() != initialAlpha) |
||
| 119 | return true; |
||
| 120 | |||
| 121 | counter++; |
||
| 122 | if (counter >= nextProbCheck) { |
||
| 123 | nextProbCheck = counter + 1 + counter / 4; |
||
| 124 | int bestPrio = pd.wq.getBestPrio(); |
||
| 125 | // log([&](std::ostream& os){os << "shouldStop, th:" << wt.getThreadNo() << " myP:" << myPrio << " bestP:" << bestPrio;}); |
||
| 126 | if ((bestPrio > myPrio + 20) && (bestPrio >= (myPrio + (1000 - myPrio) * 0.25)) && |
||
| 127 | (sp.owningThread() != wt.getThreadNo())) |
||
| 128 | return true; |
||
| 129 | reportNodes(false); |
||
| 130 | } |
||
| 131 | |||
| 132 | return false; |
||
| 133 | } |
||
| 134 | |||
| 135 | void |
||
| 136 | ThreadStopHandler::reportNodes(bool force) { |
||
| 137 | S64 totNodes = sc.getTotalNodesThisThread(); |
||
| 138 | S64 nodes = totNodes - lastReportedNodes; |
||
| 139 | if (force || (nodes * 1024 > totalNodes)) { |
||
| 140 | lastReportedNodes = totNodes; |
||
| 141 | pd.addSearchedNodes(nodes); |
||
| 142 | S64 totTbHits = sc.getTbHitsThisThread(); |
||
| 143 | S64 tbHits = totTbHits - lastReportedTbHits; |
||
| 144 | lastReportedTbHits = totTbHits; |
||
| 145 | pd.addTbHits(tbHits); |
||
| 146 | } |
||
| 147 | } |
||
| 148 | |||
| 149 | void |
||
| 150 | WorkerThread::mainLoop(int minProbeDepth) { |
||
| 151 | // log([&](std::ostream& os){os << "mainLoop, th:" << threadNo;}); |
||
| 152 | Numa::instance().bindThread(threadNo); |
||
| 153 | if (!et) |
||
| 154 | et = Evaluate::getEvalHashTables(); |
||
| 155 | if (!kt) |
||
| 156 | kt = std::make_shared<KillerTable>(); |
||
| 157 | if (!ht) |
||
| 158 | ht = std::make_shared<History>(); |
||
| 159 | |||
| 160 | TreeLogger logFile; |
||
| 161 | logFile.open("/home/petero/treelog.dmp", pd, threadNo); |
||
| 162 | |||
| 163 | // UtilizationTimer uTimer; |
||
| 164 | std::mutex m; |
||
| 165 | std::unique_lock<std::mutex> lock(m); |
||
| 166 | Position pos; |
||
| 167 | std::shared_ptr<SplitPoint> sp; |
||
| 168 | for (int iter = 0; ; iter++) { |
||
| 169 | const bool doTiming = (iter & 15) == 0; |
||
| 170 | int moveNo = -1; |
||
| 171 | // uTimer.setPUseful(-1); |
||
| 172 | const double t0 = doTiming ? currentTime() : -1; |
||
| 173 | int prio; |
||
| 174 | std::shared_ptr<SplitPoint> newSp = pd.wq.getWork(moveNo, pd, threadNo, prio, pUseful); |
||
| 175 | if (!newSp) |
||
| 176 | break; |
||
| 177 | const double tStart = doTiming ? currentTime() : -1; |
||
| 178 | |||
| 179 | const SplitPointMove& spMove = newSp->getSpMove(moveNo); |
||
| 180 | const int depth = spMove.getDepth(); |
||
| 181 | if (depth < 0) { // Move skipped by forward pruning or legality check |
||
| 182 | pd.wq.moveFinished(newSp, moveNo, false, SearchConst::UNKNOWN_SCORE); |
||
| 183 | continue; |
||
| 184 | } |
||
| 185 | if (sp != newSp) { |
||
| 186 | sp = newSp; |
||
| 187 | *ht = sp->getHistory(); |
||
| 188 | *kt = sp->getKillerTable(); |
||
| 189 | } |
||
| 190 | Search::SearchTables st(tt, *kt, *ht, *et); |
||
| 191 | sp->getPos(pos, spMove.getMove()); |
||
| 192 | std::vector<U64> posHashList; |
||
| 193 | int posHashListSize; |
||
| 194 | sp->getPosHashList(pos, posHashList, posHashListSize); |
||
| 195 | Search sc(pos, posHashList, posHashListSize, st, pd, sp, logFile); |
||
| 196 | const U64 rootNodeIdx = logFile.logPosition(pos, sp->owningThread(), |
||
| 197 | sp->getSearchTreeInfo().nodeIdx, moveNo); |
||
| 198 | sc.setThreadNo(threadNo); |
||
| 199 | sc.setMinProbeDepth(minProbeDepth); |
||
| 200 | const int alpha = sp->getAlpha(); |
||
| 201 | const int beta = sp->getBeta(); |
||
| 202 | const S64 nodes0 = pd.getNumSearchedNodes(); |
||
| 203 | auto stopHandler(std::make_shared<ThreadStopHandler>(*this, pd, *sp, spMove, |
||
| 204 | sc, alpha, nodes0, prio)); |
||
| 205 | sc.setStopHandler(stopHandler); |
||
| 206 | const int ply = sp->getPly(); |
||
| 207 | const int lmr = spMove.getLMR(); |
||
| 208 | const int captSquare = spMove.getRecaptureSquare(); |
||
| 209 | const bool inCheck = spMove.getInCheck(); |
||
| 210 | sc.setSearchTreeInfo(ply, sp->getSearchTreeInfo(), spMove.getMove(), moveNo, lmr, rootNodeIdx); |
||
| 211 | try { |
||
| 212 | // log([&](std::ostream& os){os << "th:" << threadNo << " seqNo:" << sp->getSeqNo() << " ply:" << ply |
||
| 213 | // << " c:" << sp->getCurrMoveNo() << " m:" << moveNo |
||
| 214 | // << " a:" << alpha << " b:" << beta |
||
| 215 | // << " d:" << depth/SearchConst::plyScale |
||
| 216 | // << " p:" << sp->getPMoveUseful(pd.fhInfo, moveNo) << " start";}); |
||
| 217 | // uTimer.setPUseful(pUseful); |
||
| 218 | const bool smp = pd.numHelperThreads() > 1; |
||
| 219 | int score = -sc.negaScout(smp, true, -(alpha+1), -alpha, ply+1, |
||
| 220 | depth, captSquare, inCheck); |
||
| 221 | if (((lmr > 0) && (score > alpha)) || |
||
| 222 | ((score > alpha) && (score < beta))) { |
||
| 223 | sc.setSearchTreeInfo(ply, sp->getSearchTreeInfo(), spMove.getMove(), moveNo, 0, rootNodeIdx); |
||
| 224 | score = -sc.negaScout(smp, true, -beta, -alpha, ply+1, |
||
| 225 | depth + lmr, captSquare, inCheck); |
||
| 226 | } |
||
| 227 | // uTimer.setPUseful(0); |
||
| 228 | bool cancelRemaining = score >= beta; |
||
| 229 | // log([&](std::ostream& os){os << "th:" << threadNo << " seqNo:" << sp->getSeqNo() << " ply:" << ply |
||
| 230 | // << " c:" << sp->getCurrMoveNo() << " m:" << moveNo |
||
| 231 | // << " a:" << alpha << " b:" << beta << " s:" << score |
||
| 232 | // << " d:" << depth/SearchConst::plyScale << " n:" << sc.getTotalNodesThisThread();}); |
||
| 233 | pd.wq.moveFinished(sp, moveNo, cancelRemaining, score); |
||
| 234 | } catch (const Search::StopSearch&) { |
||
| 235 | // log([&](std::ostream& os){os << "th:" << threadNo << " seqNo:" << sp->getSeqNo() << " m:" << moveNo |
||
| 236 | // << " aborted n:" << sc.getTotalNodesThisThread();}); |
||
| 237 | if (!spMove.isCanceled() && !pd.wq.isStopped()) |
||
| 238 | pd.wq.returnMove(sp, moveNo); |
||
| 239 | } |
||
| 240 | if (doTiming) { |
||
| 241 | const double tEnd = currentTime(); |
||
| 242 | pd.npsInfo.addData(sp->getDepth(), sc.getTotalNodesThisThread(), tStart - t0, tEnd - tStart); |
||
| 243 | } |
||
| 244 | pUseful = 0.0; |
||
| 245 | } |
||
| 246 | // double tElapsed, tUseful, tSleep; |
||
| 247 | // uTimer.getStats(tElapsed, tUseful, tSleep); |
||
| 248 | // log([&](std::ostream& os){ |
||
| 249 | // os << "~mainLoop, th:" << threadNo << " useful:" << tUseful / tElapsed |
||
| 250 | // << " sleep:" << tSleep / tElapsed; |
||
| 251 | // }); |
||
| 252 | // log([&](std::ostream& os){os << "~mainLoop, th:" << threadNo;}); |
||
| 253 | } |
||
| 254 | |||
| 255 | // ---------------------------------------------------------------------------- |
||
| 256 | |||
| 257 | void |
||
| 258 | WorkQueue::setStopped(bool stop) { |
||
| 259 | Lock L(this); |
||
| 260 | stopped = stop; |
||
| 261 | if (stopped) |
||
| 262 | cv.notify_all(); |
||
| 263 | } |
||
| 264 | |||
| 265 | void |
||
| 266 | WorkQueue::addWork(const std::shared_ptr<SplitPoint>& sp) { |
||
| 267 | Lock L(this); |
||
| 268 | // ScopedTimeSample sts { getAddWorkStat(sp->owningThread()) }; |
||
| 269 | addWorkInternal(sp); |
||
| 270 | } |
||
| 271 | |||
| 272 | void |
||
| 273 | WorkQueue::tryAddWork(const std::shared_ptr<SplitPoint>& sp, |
||
| 274 | std::vector<std::shared_ptr<SplitPoint>>& pending) { |
||
| 275 | std::unique_lock<std::mutex> lock(mutex, std::defer_lock); |
||
| 276 | if (!lock.try_lock()) { |
||
| 277 | pending.push_back(sp); |
||
| 278 | return; |
||
| 279 | } |
||
| 280 | for (const auto& sp : pending) |
||
| 281 | addWorkInternal(sp); |
||
| 282 | pending.clear(); |
||
| 283 | addWorkInternal(sp); |
||
| 284 | } |
||
| 285 | |||
| 286 | void |
||
| 287 | WorkQueue::addWorkInternal(const std::shared_ptr<SplitPoint>& sp) { |
||
| 288 | sp->setSeqNo(); |
||
| 289 | std::shared_ptr<SplitPoint> parent = sp->getParent(); |
||
| 290 | if (parent) { |
||
| 291 | if (parent->isCanceled()) |
||
| 292 | sp->cancel(); |
||
| 293 | else |
||
| 294 | parent->addChild(sp); |
||
| 295 | } |
||
| 296 | if (sp->hasUnFinishedMove()) { |
||
| 297 | sp->computeProbabilities(fhInfo, npsInfo); |
||
| 298 | insertInQueue(sp); |
||
| 299 | } |
||
| 300 | } |
||
| 301 | |||
| 302 | std::shared_ptr<SplitPoint> |
||
| 303 | WorkQueue::getWork(int& spMove, ParallelData& pd, int threadNo) { |
||
| 304 | int prio; |
||
| 305 | double pUseful; |
||
| 306 | return getWork(spMove, pd, threadNo, prio, pUseful); |
||
| 307 | } |
||
| 308 | |||
| 309 | std::shared_ptr<SplitPoint> |
||
| 310 | WorkQueue::getWork(int& spMove, ParallelData& pd, int threadNo, int& prio, double& pUseful) { |
||
| 311 | Lock L(this); |
||
| 312 | while (true) { |
||
| 313 | while (queue.empty() && !isStopped()) |
||
| 314 | L.wait(cv); |
||
| 315 | // ScopedTimeSample sts { getGetWorkStat(threadNo) }; |
||
| 316 | if (isStopped()) |
||
| 317 | return nullptr; |
||
| 318 | std::shared_ptr<SplitPoint> ret = queue.front(); |
||
| 319 | spMove = ret->getNextMove(pd.fhInfo); |
||
| 320 | if (spMove < 0) { |
||
| 321 | L.wait(cv); |
||
| 322 | continue; |
||
| 323 | } |
||
| 324 | // log([&](std::ostream& os){printSpTree(os, pd, threadNo, ret, spMove);}); |
||
| 325 | prio = ret->getPrio(); |
||
| 326 | updateProbabilities(ret); |
||
| 327 | pUseful = ret->getPMoveUseful(pd.fhInfo, spMove); |
||
| 328 | return ret; |
||
| 329 | } |
||
| 330 | } |
||
| 331 | |||
| 332 | void |
||
| 333 | WorkQueue::returnMove(const std::shared_ptr<SplitPoint>& sp, int moveNo) { |
||
| 334 | Lock L(this); |
||
| 335 | sp->returnMove(moveNo); |
||
| 336 | updateProbabilities(sp); |
||
| 337 | } |
||
| 338 | |||
| 339 | int |
||
| 340 | WorkQueue::setOwnerCurrMove(const std::shared_ptr<SplitPoint>& sp, int moveNo, int alpha) { |
||
| 341 | Lock L(this); |
||
| 342 | int score = sp->setOwnerCurrMove(moveNo, alpha); |
||
| 343 | updateProbabilities(sp); |
||
| 344 | return score; |
||
| 345 | } |
||
| 346 | |||
| 347 | void |
||
| 348 | WorkQueue::cancel(const std::shared_ptr<SplitPoint>& sp) { |
||
| 349 | Lock L(this); |
||
| 350 | cancelInternal(sp); |
||
| 351 | } |
||
| 352 | |||
| 353 | void |
||
| 354 | WorkQueue::moveFinished(const std::shared_ptr<SplitPoint>& sp, int moveNo, |
||
| 355 | bool cancelRemaining, int score) { |
||
| 356 | Lock L(this); |
||
| 357 | sp->moveFinished(moveNo, cancelRemaining, score); |
||
| 358 | updateProbabilities(sp); |
||
| 359 | } |
||
| 360 | |||
| 361 | double |
||
| 362 | WorkQueue::getBestProbability(std::shared_ptr<SplitPoint>& bestSp) const { |
||
| 363 | Lock L(this); |
||
| 364 | if (queue.empty()) |
||
| 365 | return 0.0; |
||
| 366 | bestSp = queue.front(); |
||
| 367 | return bestSp->getPNextMoveUseful(); |
||
| 368 | } |
||
| 369 | |||
| 370 | int |
||
| 371 | WorkQueue::getBestPrio() const { |
||
| 372 | Lock L(this); |
||
| 373 | if (queue.empty()) |
||
| 374 | return -1; |
||
| 375 | return queue.front()->getPrio(); |
||
| 376 | } |
||
| 377 | |||
| 378 | double |
||
| 379 | WorkQueue::getBestProbability() const { |
||
| 380 | std::shared_ptr<SplitPoint> bestSp; |
||
| 381 | return getBestProbability(bestSp); |
||
| 382 | } |
||
| 383 | |||
| 384 | void |
||
| 385 | WorkQueue::updateProbabilities(const std::shared_ptr<SplitPoint>& sp) { |
||
| 386 | if (!sp->hasUnFinishedMove()) |
||
| 387 | queue.remove(sp); |
||
| 388 | sp->computeProbabilities(fhInfo, npsInfo); |
||
| 389 | } |
||
| 390 | |||
| 391 | void |
||
| 392 | WorkQueue::cancelInternal(const std::shared_ptr<SplitPoint>& sp) { |
||
| 393 | sp->cancel(); |
||
| 394 | queue.remove(sp); |
||
| 395 | |||
| 396 | for (const auto& wChild : sp->getChildren()) { |
||
| 397 | std::shared_ptr<SplitPoint> child = wChild.lock(); |
||
| 398 | if (child) |
||
| 399 | cancelInternal(child); |
||
| 400 | } |
||
| 401 | } |
||
| 402 | |||
| 403 | static std::string toPercentStr(double p) { |
||
| 404 | std::stringstream ss; |
||
| 405 | int pc = (int)(p * 100); |
||
| 406 | if (pc == 100) |
||
| 407 | pc = 99; |
||
| 408 | ss << std::setfill('0') << std::setw(2) << pc; |
||
| 409 | return ss.str(); |
||
| 410 | } |
||
| 411 | |||
| 412 | void |
||
| 413 | WorkQueue::printSpTree(std::ostream& os, const ParallelData& pd, |
||
| 414 | int threadNo, const std::shared_ptr<SplitPoint> selectedSp, |
||
| 415 | int selectedMove) { |
||
| 416 | const int numThreads = pd.numHelperThreads() + 1; |
||
| 417 | std::shared_ptr<SplitPoint> rootSp = queue.front(); |
||
| 418 | assert(rootSp); |
||
| 419 | while (rootSp->getParent()) |
||
| 420 | rootSp = rootSp->getParent(); |
||
| 421 | std::vector<int> parentThreads(numThreads, -1); |
||
| 422 | std::vector<std::shared_ptr<SplitPoint>> leaves(numThreads, nullptr); |
||
| 423 | findLeaves(rootSp, parentThreads, leaves); |
||
| 424 | |||
| 425 | os << "th:" << threadNo << " m: " << selectedMove << ':' |
||
| 426 | << TextIO::moveToUCIString(selectedSp->getSpMove(selectedMove).getMove()) |
||
| 427 | << " p:" << selectedSp->getPMoveUseful(pd.fhInfo, selectedMove) |
||
| 428 | << std::endl; |
||
| 429 | for (int i = 0; i < numThreads; i++) { |
||
| 430 | std::vector<std::shared_ptr<SplitPoint>> thVec; |
||
| 431 | for (auto sp = leaves[i]; sp; sp = sp->getParent()) |
||
| 432 | thVec.push_back(sp); |
||
| 433 | std::reverse(thVec.begin(), thVec.end()); |
||
| 434 | os << "th " << i << ' '; |
||
| 435 | if (parentThreads[i] < 0) |
||
| 436 | os << '-'; |
||
| 437 | else |
||
| 438 | os << parentThreads[i]; |
||
| 439 | os << ' ' << toPercentStr(i == 0 ? 1 : pd.getHelperThread(i-1).getPUseful()); |
||
| 440 | if (!thVec.empty()) { |
||
| 441 | for (const auto& sp : thVec) |
||
| 442 | if (sp->owningThread() == i) { |
||
| 443 | os << ' ' << std::setw(6) << sp->getSeqNo(); |
||
| 444 | break; |
||
| 445 | } |
||
| 446 | } |
||
| 447 | for (const auto& sp : thVec) { |
||
| 448 | if (sp->owningThread() == i) { |
||
| 449 | int pMove = sp->getParentMoveNo(); |
||
| 450 | os << ' ' << std::setw(2) << pMove << (sp == selectedSp ? '*' : ':'); |
||
| 451 | if (pMove < 0) |
||
| 452 | os << "null"; |
||
| 453 | else if (sp->getParent()) |
||
| 454 | os << TextIO::moveToUCIString(sp->getParent()->getSpMove(pMove).getMove()); |
||
| 455 | else |
||
| 456 | os << TextIO::moveToUCIString(pd.topMove); |
||
| 457 | os << ',' << toPercentStr(sp->getPSpUseful()) |
||
| 458 | << ':' << toPercentStr(sp->getPNextMoveUseful()); |
||
| 459 | os << ',' << std::setw(2) << sp->getCurrMoveNo() << ':' << std::setw(2) << sp->findNextMove(pd.fhInfo); |
||
| 460 | } else { |
||
| 461 | os << " "; |
||
| 462 | } |
||
| 463 | } |
||
| 464 | os << std::endl; |
||
| 465 | } |
||
| 466 | } |
||
| 467 | |||
| 468 | void |
||
| 469 | WorkQueue::findLeaves(const std::shared_ptr<SplitPoint>& sp, |
||
| 470 | std::vector<int>& parentThreads, |
||
| 471 | std::vector<std::shared_ptr<SplitPoint>>& leaves) { |
||
| 472 | bool isLeaf = true; |
||
| 473 | const std::vector<std::weak_ptr<SplitPoint>>& children = sp->getChildren(); |
||
| 474 | for (const auto& wChild : children) { |
||
| 475 | std::shared_ptr<SplitPoint> child = wChild.lock(); |
||
| 476 | if (child && !child->isCanceled()) { |
||
| 477 | if (child->owningThread() == sp->owningThread()) { |
||
| 478 | isLeaf = false; |
||
| 479 | } else { |
||
| 480 | assert(parentThreads[child->owningThread()] == -1); |
||
| 481 | parentThreads[child->owningThread()] = sp->owningThread(); |
||
| 482 | } |
||
| 483 | findLeaves(child, parentThreads, leaves); |
||
| 484 | } |
||
| 485 | } |
||
| 486 | if (isLeaf) { |
||
| 487 | assert(sp->owningThread() >= 0); |
||
| 488 | assert(sp->owningThread() < (int)leaves.size()); |
||
| 489 | leaves[sp->owningThread()] = sp; |
||
| 490 | } |
||
| 491 | } |
||
| 492 | |||
| 493 | WorkQueue::Lock::Lock(const WorkQueue* wq0) |
||
| 494 | : wq(*wq0), lock(wq.mutex, std::defer_lock) { |
||
| 495 | bool contended = false; |
||
| 496 | if (!lock.try_lock()) { |
||
| 497 | contended = true; |
||
| 498 | lock.lock(); |
||
| 499 | } |
||
| 500 | if (wq.queue.empty()) |
||
| 501 | contended = false; |
||
| 502 | U64 c = wq.nContended; |
||
| 503 | U64 n = wq.nNonContended; |
||
| 504 | if (contended) |
||
| 505 | c++; |
||
| 506 | else |
||
| 507 | n++; |
||
| 508 | if (n + c > 30000) { |
||
| 509 | c /= 2; |
||
| 510 | n /= 2; |
||
| 511 | if (c * 100 > n * 50) { |
||
| 512 | wq.minSplitDepth++; |
||
| 513 | // std::cout << "contended stat: " << wq.minSplitDepth << " " << c << " " << n << std::endl; |
||
| 514 | } else if ((c * 100 < n * 25) && (wq.minSplitDepth > SearchConst::MIN_SMP_DEPTH)) { |
||
| 515 | wq.minSplitDepth--; |
||
| 516 | // std::cout << "contended stat: " << wq.minSplitDepth << " " << c << " " << n << std::endl; |
||
| 517 | } |
||
| 518 | } |
||
| 519 | wq.nContended = c; |
||
| 520 | wq.nNonContended = n; |
||
| 521 | } |
||
| 522 | |||
| 523 | // ---------------------------------------------------------------------------- |
||
| 524 | |||
| 525 | void |
||
| 526 | ParallelData::addRemoveWorkers(int numWorkers) { |
||
| 527 | while (numWorkers < (int)threads.size()) { |
||
| 528 | assert(!threads.back()->threadRunning()); |
||
| 529 | threads.pop_back(); |
||
| 530 | } |
||
| 531 | for (int i = threads.size(); i < numWorkers; i++) |
||
| 532 | threads.push_back(std::make_shared<WorkerThread>(i+1, *this, tt)); |
||
| 533 | } |
||
| 534 | |||
| 535 | void |
||
| 536 | ParallelData::startAll() { |
||
| 537 | totalHelperNodes = 0; |
||
| 538 | helperTbHits = 0; |
||
| 539 | wq.setStopped(false); |
||
| 540 | for (auto& thread : threads) |
||
| 541 | thread->start(); |
||
| 542 | } |
||
| 543 | |||
| 544 | void |
||
| 545 | ParallelData::stopAll() { |
||
| 546 | wq.setStopped(true); |
||
| 547 | for (auto& thread : threads) |
||
| 548 | thread->join(); |
||
| 549 | } |
||
| 550 | |||
| 551 | // ---------------------------------------------------------------------------- |
||
| 552 | |||
| 553 | SplitPoint::SplitPoint(int threadNo0, |
||
| 554 | const std::shared_ptr<SplitPoint>& parentSp0, int parentMoveNo0, |
||
| 555 | const Position& pos0, const std::vector<U64>& posHashList0, |
||
| 556 | int posHashListSize0, const SearchTreeInfo& sti0, |
||
| 557 | const KillerTable& kt0, const History& ht0, |
||
| 558 | int alpha0, int beta0, int ply0, int depth0) |
||
| 559 | : pos(pos0), posHashList(posHashList0), posHashListSize(posHashListSize0), |
||
| 560 | searchTreeInfo(sti0), kt(kt0), ht(ht0), |
||
| 561 | alpha(alpha0), beta(beta0), ply(ply0), depth(depth0), |
||
| 562 | isPV(beta0 > alpha0 + 1), |
||
| 563 | pSpUseful(0.0), pNextMoveUseful(0.0), |
||
| 564 | threadNo(threadNo0), parent(parentSp0), parentMoveNo(parentMoveNo0), |
||
| 565 | seqNo(0), currMoveNo(0), inserted(false), canceled(false) { |
||
| 566 | } |
||
| 567 | |||
| 568 | void |
||
| 569 | SplitPoint::addMove(int moveNo, const SplitPointMove& spMove) { |
||
| 570 | assert(moveNo >= (int)spMoves.size()); |
||
| 571 | while ((int)spMoves.size() < moveNo) |
||
| 572 | spMoves.push_back(SplitPointMove(Move(), 0, -1, -1, false)); |
||
| 573 | spMoves.push_back(spMove); |
||
| 574 | } |
||
| 575 | |||
| 576 | void |
||
| 577 | SplitPoint::computeProbabilities(const FailHighInfo& fhInfo, const DepthNpsInfo& npsInfo) { |
||
| 578 | if (parent) { |
||
| 579 | double pMoveUseful = 1.0; |
||
| 580 | if (parentMoveNo >= 0) |
||
| 581 | pMoveUseful = parent->getMoveNeededProbability(fhInfo, parentMoveNo); |
||
| 582 | pSpUseful = parent->pSpUseful * pMoveUseful; |
||
| 583 | } else { |
||
| 584 | pSpUseful = 1.0; |
||
| 585 | } |
||
| 586 | double pNextUseful = getMoveNeededProbability(fhInfo, findNextMove(fhInfo)); |
||
| 587 | pNextMoveUseful = pSpUseful * pNextUseful; |
||
| 588 | newPrio(getSpPrio(npsInfo)); |
||
| 589 | |||
| 590 | bool deleted = false; |
||
| 591 | for (const auto& wChild : children) { |
||
| 592 | std::shared_ptr<SplitPoint> child = wChild.lock(); |
||
| 593 | if (child) |
||
| 594 | child->computeProbabilities(fhInfo, npsInfo); |
||
| 595 | else |
||
| 596 | deleted = true; |
||
| 597 | } |
||
| 598 | if (deleted) |
||
| 599 | cleanUpChildren(); |
||
| 600 | } |
||
| 601 | |||
| 602 | double |
||
| 603 | SplitPoint::getPMoveUseful(const FailHighInfo& fhInfo, int moveNo) const { |
||
| 604 | return pSpUseful * getMoveNeededProbability(fhInfo, moveNo); |
||
| 605 | } |
||
| 606 | |||
| 607 | void |
||
| 608 | SplitPoint::getPos(Position& pos, const Move& move) const { |
||
| 609 | pos = this->pos; |
||
| 610 | UndoInfo ui; |
||
| 611 | pos.makeMove(move, ui); |
||
| 612 | } |
||
| 613 | |||
| 614 | void |
||
| 615 | SplitPoint::getPosHashList(const Position& pos, std::vector<U64>& posHashList, |
||
| 616 | int& posHashListSize) const { |
||
| 617 | posHashList = this->posHashList; |
||
| 618 | posHashListSize = this->posHashListSize; |
||
| 619 | posHashList[posHashListSize++] = pos.zobristHash(); |
||
| 620 | } |
||
| 621 | |||
| 622 | int |
||
| 623 | SplitPoint::getNextMove(const FailHighInfo& fhInfo) { |
||
| 624 | int m = findNextMove(fhInfo); |
||
| 625 | if (m < 0) |
||
| 626 | return m; |
||
| 627 | spMoves[m].setSearching(true); |
||
| 628 | return m; |
||
| 629 | } |
||
| 630 | |||
| 631 | void |
||
| 632 | SplitPoint::moveFinished(int moveNo, bool cancelRemaining, int score) { |
||
| 633 | assert((moveNo >= 0) && (moveNo < (int)spMoves.size())); |
||
| 634 | spMoves[moveNo].setScore(score); |
||
| 635 | spMoves[moveNo].setSearching(false); |
||
| 636 | spMoves[moveNo].setCanceled(true); |
||
| 637 | if (cancelRemaining) |
||
| 638 | for (int i = moveNo+1; i < (int)spMoves.size(); i++) |
||
| 639 | spMoves[i].setCanceled(true); |
||
| 640 | } |
||
| 641 | |||
| 642 | bool |
||
| 643 | SplitPoint::hasUnStartedMove() const { |
||
| 644 | if (canceled) |
||
| 645 | return false; |
||
| 646 | for (int i = currMoveNo + 1; i < (int)spMoves.size(); i++) |
||
| 647 | if (!spMoves[i].isCanceled() && !spMoves[i].isSearching()) |
||
| 648 | return true; |
||
| 649 | return false; |
||
| 650 | } |
||
| 651 | |||
| 652 | bool |
||
| 653 | SplitPoint::hasUnFinishedMove() const { |
||
| 654 | if (canceled) |
||
| 655 | return false; |
||
| 656 | for (int i = currMoveNo + 1; i < (int)spMoves.size(); i++) |
||
| 657 | if (!spMoves[i].isCanceled()) |
||
| 658 | return true; |
||
| 659 | return false; |
||
| 660 | } |
||
| 661 | |||
| 662 | int |
||
| 663 | SplitPoint::findNextMove(const FailHighInfo& fhInfo) const { |
||
| 664 | int i0 = -1; |
||
| 665 | const double pGood = 0.98; |
||
| 666 | for (int i = currMoveNo+1; i < (int)spMoves.size(); i++) |
||
| 667 | if (!spMoves[i].isCanceled() && !spMoves[i].isSearching()) { |
||
| 668 | if ((getPNextMoveUseful() > pGood) && (i0 == -1)) |
||
| 669 | i0 = i; |
||
| 670 | else { |
||
| 671 | if ((i0 != -1) && (getPMoveUseful(fhInfo, i) <= pGood)) |
||
| 672 | return i0; |
||
| 673 | return i; |
||
| 674 | } |
||
| 675 | } |
||
| 676 | return i0; |
||
| 677 | } |
||
| 678 | |||
| 679 | double |
||
| 680 | SplitPoint::getMoveNeededProbability(const FailHighInfo& fhInfo, int moveNo) const { |
||
| 681 | if (isPvNode()) |
||
| 682 | return fhInfo.getMoveNeededProbabilityPv(currMoveNo, moveNo); |
||
| 683 | else |
||
| 684 | return fhInfo.getMoveNeededProbability(parentMoveNo, |
||
| 685 | currMoveNo, |
||
| 686 | moveNo, isAllNode()); |
||
| 687 | } |
||
| 688 | |||
| 689 | void |
||
| 690 | SplitPoint::cleanUpChildren() { |
||
| 691 | std::vector<std::weak_ptr<SplitPoint>> toKeep; |
||
| 692 | for (const auto& wChild : children) |
||
| 693 | if (wChild.lock()) |
||
| 694 | toKeep.push_back(wChild); |
||
| 695 | children = toKeep; |
||
| 696 | } |
||
| 697 | |||
| 698 | bool |
||
| 699 | SplitPoint::hasHelperThread() const { |
||
| 700 | for (const auto& wChild : children) { |
||
| 701 | std::shared_ptr<SplitPoint> child = wChild.lock(); |
||
| 702 | if (child && child->owningThread() != owningThread()) |
||
| 703 | return true; |
||
| 704 | } |
||
| 705 | return false; |
||
| 706 | } |
||
| 707 | |||
| 708 | bool |
||
| 709 | SplitPoint::isAncestorTo(const SplitPoint& sp) const { |
||
| 710 | const SplitPoint* tmp = &sp; |
||
| 711 | while (tmp) { |
||
| 712 | if (tmp == this) |
||
| 713 | return true; |
||
| 714 | tmp = &*(tmp->parent); |
||
| 715 | } |
||
| 716 | return false; |
||
| 717 | } |
||
| 718 | |||
| 719 | bool |
||
| 720 | SplitPoint::isAllNode() const { |
||
| 721 | int nFirst = 0; |
||
| 722 | const SplitPoint* tmp = this; |
||
| 723 | while (tmp) { |
||
| 724 | if (tmp->parentMoveNo == 0) |
||
| 725 | nFirst++; |
||
| 726 | else |
||
| 727 | break; |
||
| 728 | tmp = &*(tmp->parent); |
||
| 729 | } |
||
| 730 | return (nFirst % 2) != 0; |
||
| 731 | } |
||
| 732 | |||
| 733 | void |
||
| 734 | SplitPoint::print(std::ostream& os, int level, const FailHighInfo& fhInfo) const { |
||
| 735 | std::string pad(level*2, ' '); |
||
| 736 | os << pad << "seq:" << seqNo << " pos:" << TextIO::toFEN(pos) << std::endl; |
||
| 737 | os << pad << "parent:" << parentMoveNo << " hashListSize:" << posHashListSize << |
||
| 738 | " a:" << alpha << " b:" << beta << " ply:" << ply << " canceled:" << canceled << std::endl; |
||
| 739 | os << pad << "p1:" << pSpUseful << " p2:" << pNextMoveUseful << " curr:" << currMoveNo << std::endl; |
||
| 740 | os << pad << "moves:"; |
||
| 741 | for (int mi = 0; mi < (int)spMoves.size(); mi++) { |
||
| 742 | const auto& spm = spMoves[mi]; |
||
| 743 | os << ' ' << TextIO::moveToUCIString(spm.getMove()); |
||
| 744 | if (spm.isCanceled()) |
||
| 745 | os << ",c"; |
||
| 746 | if (spm.isSearching()) |
||
| 747 | os << ",s"; |
||
| 748 | os << "," << getMoveNeededProbability(fhInfo, mi); |
||
| 749 | } |
||
| 750 | os << std::endl; |
||
| 751 | for (const auto& wChild : children) { |
||
| 752 | std::shared_ptr<SplitPoint> child = wChild.lock(); |
||
| 753 | if (child) |
||
| 754 | child->print(os, level+1, fhInfo); |
||
| 755 | } |
||
| 756 | } |
||
| 757 | |||
| 758 | // ---------------------------------------------------------------------------- |
||
| 759 | |||
| 760 | void |
||
| 761 | SplitPointHolder::setSp(const std::shared_ptr<SplitPoint>& sp0) { |
||
| 762 | assert(state == State::EMPTY); |
||
| 763 | assert(sp0); |
||
| 764 | sp = sp0; |
||
| 765 | if (sp->getBeta() > sp->getAlpha() + 1) |
||
| 766 | pd.fhInfo.addPvData(-1, false); |
||
| 767 | state = State::CREATED; |
||
| 768 | } |
||
| 769 | |||
| 770 | void |
||
| 771 | SplitPointHolder::addToQueue() { |
||
| 772 | assert(state == State::CREATED); |
||
| 773 | pd.wq.tryAddWork(sp, pending); |
||
| 774 | spVec.push_back(sp); |
||
| 775 | // log([&](std::ostream& os){os << "add seqNo:" << sp->getSeqNo() << " ply:" << sp->getPly() |
||
| 776 | // << " pNext:" << sp->getPNextMoveUseful() |
||
| 777 | // << " pMove:" << sp->getParentMoveNo() << " vec:" << spVec.size();}); |
||
| 778 | state = State::QUEUED; |
||
| 779 | } |
||
| 780 | |||
| 781 | // ---------------------------------------------------------------------------- |
||
| 782 | |||
| 783 | FailHighInfo::FailHighInfo() |
||
| 784 | : totCount(0) { |
||
| 785 | for (int i = 0; i < NUM_NODE_TYPES; i++) |
||
| 786 | failLoCount[i] = 0; |
||
| 787 | for (int j = 0; j < NUM_STAT_MOVES; j++) |
||
| 788 | newAlpha[j] = 0; |
||
| 789 | totPvCount = 0; |
||
| 790 | } |
||
| 791 | |||
| 792 | double |
||
| 793 | FailHighInfo::getMoveNeededProbability(int parentMoveNo, |
||
| 794 | int currMoveNo, int moveNo, bool allNode) const { |
||
| 795 | const int pIdx = getNodeType(parentMoveNo, allNode); |
||
| 796 | moveNo = std::min(moveNo, NUM_STAT_MOVES-1); |
||
| 797 | if (moveNo < 0) |
||
| 798 | return 0.0; |
||
| 799 | |||
| 800 | int nNeeded = failLoCount[pIdx] + failHiCount[pIdx].sum(moveNo, NUM_STAT_MOVES); |
||
| 801 | int nTotal = nNeeded + failHiCount[pIdx].sum(currMoveNo, moveNo); |
||
| 802 | |||
| 803 | return (nTotal > 0) ? nNeeded / (double)nTotal : 0.5; |
||
| 804 | } |
||
| 805 | |||
| 806 | double |
||
| 807 | FailHighInfo::getMoveNeededProbabilityPv(int currMoveNo, int moveNo) const { |
||
| 808 | moveNo = std::min(moveNo, NUM_STAT_MOVES-1); |
||
| 809 | if (moveNo < 0) |
||
| 810 | return 0.0; |
||
| 811 | if (totPvCount <= 0) |
||
| 812 | return 0.5; |
||
| 813 | |||
| 814 | double prob = 1.0; |
||
| 815 | double inv = 1.0 / totPvCount; |
||
| 816 | for (int i = currMoveNo; i < moveNo; i++) |
||
| 817 | prob *= std::max(0.0, 1.0 - newAlpha[i] * inv); |
||
| 818 | return prob; |
||
| 819 | } |
||
| 820 | |||
| 821 | void |
||
| 822 | FailHighInfo::addData(int parentMoveNo, int nSearched, bool failHigh, bool allNode) { |
||
| 823 | if (nSearched < 0) |
||
| 824 | return; |
||
| 825 | const int pIdx = getNodeType(parentMoveNo, allNode); |
||
| 826 | if (failHigh) { |
||
| 827 | nSearched = std::min(nSearched, NUM_STAT_MOVES-1); |
||
| 828 | failHiCount[pIdx].add(nSearched, 1); |
||
| 829 | } else { |
||
| 830 | failLoCount[pIdx]++; |
||
| 831 | } |
||
| 832 | totCount++; |
||
| 833 | if (totCount >= 1000000) { |
||
| 834 | std::lock_guard<std::mutex> L(mutex); |
||
| 835 | if (totCount >= 1000000) |
||
| 836 | reScaleInternal(2); |
||
| 837 | } |
||
| 838 | } |
||
| 839 | |||
| 840 | void |
||
| 841 | FailHighInfo::addPvData(int nSearched, bool alphaChanged) { |
||
| 842 | if (nSearched >= 0) { |
||
| 843 | if (alphaChanged && nSearched < NUM_STAT_MOVES) |
||
| 844 | newAlpha[nSearched]++; |
||
| 845 | } else { |
||
| 846 | totPvCount++; |
||
| 847 | if (totPvCount >= 10000) { |
||
| 848 | std::lock_guard<std::mutex> L(mutex); |
||
| 849 | if (totPvCount >= 10000) |
||
| 850 | reScalePv(2); |
||
| 851 | } |
||
| 852 | } |
||
| 853 | } |
||
| 854 | |||
| 855 | void |
||
| 856 | FailHighInfo::reScale() { |
||
| 857 | reScaleInternal(4); |
||
| 858 | reScalePv(4); |
||
| 859 | } |
||
| 860 | |||
| 861 | void |
||
| 862 | FailHighInfo::reScaleInternal(int factor) { |
||
| 863 | for (int i = 0; i < NUM_NODE_TYPES; i++) { |
||
| 864 | for (int j = 0; j < NUM_STAT_MOVES; j++) { |
||
| 865 | int val = failHiCount[i].get(j); |
||
| 866 | failHiCount[i].add(j, val / factor - val); |
||
| 867 | } |
||
| 868 | failLoCount[i] = failLoCount[i] / factor; |
||
| 869 | } |
||
| 870 | totCount = totCount / factor; |
||
| 871 | } |
||
| 872 | |||
| 873 | void |
||
| 874 | FailHighInfo::reScalePv(int factor) { |
||
| 875 | for (int j = 0; j < NUM_STAT_MOVES; j++) |
||
| 876 | newAlpha[j] = newAlpha[j] / factor; |
||
| 877 | totPvCount = totPvCount / factor; |
||
| 878 | } |
||
| 879 | |||
| 880 | void |
||
| 881 | FailHighInfo::print(std::ostream& os) const { |
||
| 882 | for (int i = 0; i < NUM_NODE_TYPES; i++) { |
||
| 883 | os << "fhInfo: " << i << ' ' << std::setw(6) << failLoCount[i]; |
||
| 884 | for (int j = 0; j < NUM_STAT_MOVES; j++) |
||
| 885 | os << ' ' << std::setw(6) << failHiCount[i].get(j); |
||
| 886 | os << std::endl; |
||
| 887 | } |
||
| 888 | os << "fhInfo: " << NUM_NODE_TYPES << ' ' << std::setw(6) << totPvCount; |
||
| 889 | for (int j = 0; j < NUM_STAT_MOVES; j++) |
||
| 890 | os << ' ' << std::setw(6) << newAlpha[j]; |
||
| 891 | os << std::endl; |
||
| 892 | } |
||
| 893 | |||
| 894 | // ---------------------------------------------------------------------------- |
||
| 895 | |||
| 896 | void |
||
| 897 | DepthNpsInfo::print(std::ostream& os, int iterDepth) { |
||
| 898 | std::lock_guard<std::mutex> L(mutex); |
||
| 899 | os << "npsInfo: depth:" << iterDepth << " nps0:" << nps0 |
||
| 900 | << " wait:" << waitTime / nSearches << std::endl; |
||
| 901 | for (int i = SearchConst::MIN_SMP_DEPTH; i < maxDepth; i++) { |
||
| 902 | os << "npsInfo: d:" << i |
||
| 903 | << " n:" << npsData[i].nSearches |
||
| 904 | << " nodes:" << npsData[i].nodes |
||
| 905 | << " time:" << npsData[i].time |
||
| 906 | << " nps:" << npsData[i].nodes / npsData[i].time |
||
| 907 | << " ts:" << npsData[i].nodes / (double)npsData[i].nSearches |
||
| 908 | << " eff:" << efficiencyInternal(i) << std::endl; |
||
| 909 | } |
||
| 910 | } |