X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fsearch.cpp;h=8993a4b05fbf3069c8f55e092cfa4d27ee6b8dc5;hp=047a08925a35bbd2650f1cb40b43d75cdbe377f6;hb=93349d0dbd22f063b39e6815c02835a4748fffbc;hpb=466daf6fbafd1f58f064c2fa03d2fa2c1315c4f5 diff --git a/src/search.cpp b/src/search.cpp index 047a0892..8993a4b0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -102,6 +102,48 @@ namespace { Move best = MOVE_NONE; }; + // Breadcrumbs are used to mark nodes as being searched by a given thread. + struct Breadcrumb { + std::atomic thread; + std::atomic key; + }; + std::array breadcrumbs; + + // ThreadHolding keeps track of which thread left breadcrumbs at the given node for potential reductions. + // A free node will be marked upon entering the moves loop, and unmarked upon leaving that loop, by the ctor/dtor of this struct. + struct ThreadHolding { + explicit ThreadHolding(Thread* thisThread, Key posKey, int ply) { + location = ply < 8 ? &breadcrumbs[posKey & (breadcrumbs.size() - 1)] : nullptr; + otherThread = false; + owning = false; + if (location) + { + // see if another already marked this location, if not, mark it ourselves. + Thread* tmp = (*location).thread.load(std::memory_order_relaxed); + if (tmp == nullptr) + { + (*location).thread.store(thisThread, std::memory_order_relaxed); + (*location).key.store(posKey, std::memory_order_relaxed); + owning = true; + } + else if ( tmp != thisThread + && (*location).key.load(std::memory_order_relaxed) == posKey) + otherThread = true; + } + } + + ~ThreadHolding() { + if (owning) // free the marked location. + (*location).thread.store(nullptr, std::memory_order_relaxed); + } + + bool marked() { return otherThread; } + + private: + Breadcrumb* location; + bool otherThread, owning; + }; + template Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); @@ -292,8 +334,13 @@ void Thread::search() { bestValue = delta = alpha = -VALUE_INFINITE; beta = VALUE_INFINITE; - size_t multiPV = Options["MultiPV"]; - Skill skill(Options["Skill Level"]); + multiPV = Options["MultiPV"]; + // Pick integer skill levels, but non-deterministically round up or down + // such that the average integer skill corresponds to the input floating point one. + PRNG rng(now()); + int intLevel = int(Options["Skill Level"]) + + ((Options["Skill Level"] - int(Options["Skill Level"])) * 1024 > rng.rand() % 1024 ? 1 : 0); + Skill skill(intLevel); // When playing with strength handicap enable MultiPV search that we will // use behind the scenes to retrieve a set of possible moves. @@ -847,6 +894,9 @@ moves_loop: // When in check, search starts from here moveCountPruning = false; ttCapture = ttMove && pos.capture_or_promotion(ttMove); + // Mark this node as being searched. + ThreadHolding th(thisThread, posKey, ss->ply); + // Step 12. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. while ((move = mp.next_move(moveCountPruning)) != MOVE_NONE) @@ -870,6 +920,12 @@ moves_loop: // When in check, search starts from here sync_cout << "info depth " << depth / ONE_PLY << " currmove " << UCI::move(move, pos.is_chess960()) << " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl; + + // In MultiPV mode also skip moves which will be searched later as PV moves + if (rootNode && std::count(thisThread->rootMoves.begin() + thisThread->pvIdx + 1, + thisThread->rootMoves.begin() + thisThread->multiPV, move)) + continue; + if (PvNode) (ss+1)->pv = nullptr; @@ -913,10 +969,11 @@ moves_loop: // When in check, search starts from here // Multi-cut pruning // Our ttMove is assumed to fail high, and now we failed high also on a reduced // search without the ttMove. So we assume this expected Cut-node is not singular, - // that is multiple moves fail high, and we can prune the whole subtree by returning - // the hard beta bound. - else if (cutNode && singularBeta > beta) - return beta; + // that multiple moves fail high, and we can prune the whole subtree by returning + // a soft bound. + else if ( eval >= beta + && singularBeta >= beta) + return singularBeta; } // Check extension (~2 Elo) @@ -932,7 +989,7 @@ moves_loop: // When in check, search starts from here else if ( PvNode && pos.rule50_count() > 18 && depth < 3 * ONE_PLY - && ss->ply < 3 * thisThread->rootDepth / ONE_PLY) // To avoid too deep searches + && ++thisThread->shuffleExts < thisThread->nodes.load(std::memory_order_relaxed) / 4) // To avoid too many extensions extension = ONE_PLY; // Passed pawn extension @@ -1012,6 +1069,10 @@ moves_loop: // When in check, search starts from here { Depth r = reduction(improving, depth, moveCount); + // Reduction if other threads are searching this position. + if (th.marked()) + r += ONE_PLY; + // Decrease reduction if position is or has been on the PV if (ttPv) r -= 2 * ONE_PLY;