X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fsearch.cpp;h=6232f0cbf3ab8477ff5bc10f5973a3b1a134341b;hp=c7d8d792fee4bb231a559e506ba501c7410c2177;hb=cfe59de27ddc42ac555187ae68879f2bc7bd7936;hpb=1062459029ff2ae5a717106a8634c8a1ffc15671 diff --git a/src/search.cpp b/src/search.cpp index c7d8d792..6232f0cb 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -23,6 +23,7 @@ //// #include +#include #include #include #include @@ -31,6 +32,7 @@ #include "book.h" #include "evaluate.h" #include "history.h" +#include "maxgain.h" #include "misc.h" #include "movegen.h" #include "movepick.h" @@ -192,9 +194,6 @@ namespace { /// Variables initialized by UCI options - // Minimum number of full depth (i.e. non-reduced) moves at PV and non-PV nodes - int LMRPVMoves, LMRNonPVMoves; - // Depth limit for use of dynamic threat detection Depth ThreatDepth; @@ -235,6 +234,10 @@ namespace { bool UseLogFile; std::ofstream LogFile; + // Natural logarithmic lookup table and its getter function + double lnArray[512]; + inline double ln(int i) { return lnArray[i]; } + // MP related variables int ActiveThreads = 1; Depth MinimumSplitDepth; @@ -261,6 +264,8 @@ namespace { // History table History H; + // MaxGain table + MaxGain MG; /// Functions @@ -319,13 +324,6 @@ namespace { //// Functions //// -//FIXME: HACK -static double lnArray[512]; - -inline double ln(int i) -{ - return lnArray[i]; -} /// perft() is our utility to verify move generation is bug free. All the legal /// moves up to given depth are generated and counted and the sum returned. @@ -428,8 +426,6 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move, MateThreatExtension[1] = Depth(get_option_value_int("Mate Threat Extension (PV nodes)")); MateThreatExtension[0] = Depth(get_option_value_int("Mate Threat Extension (non-PV nodes)")); - LMRPVMoves = get_option_value_int("Full Depth Moves (PV nodes)") + 1; - LMRNonPVMoves = get_option_value_int("Full Depth Moves (non-PV nodes)") + 1; ThreatDepth = get_option_value_int("Threat Depth") * OnePly; Chess960 = get_option_value_bool("UCI_Chess960"); @@ -449,6 +445,10 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move, { ActiveThreads = newActiveThreads; init_eval(ActiveThreads); + // HACK: init_eval() destroys the static castleRightsMask[] array in the + // Position class. The below line repairs the damage. + Position p(pos.to_fen()); + assert(pos.is_ok()); } // Wake up sleeping threads @@ -559,20 +559,19 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move, /// and initializes the split point stack and the global locks and condition /// objects. -#include //FIXME: HACK - void init_threads() { - // FIXME: HACK!! - for (int i = 0; i < 512; i++) - lnArray[i] = log(double(i)); - volatile int i; + bool ok; #if !defined(_MSC_VER) pthread_t pthread[1]; #endif + // Init our logarithmic lookup table + for (i = 0; i < 512; i++) + lnArray[i] = log(double(i)); // log() returns base-e logarithm + for (i = 0; i < THREAD_MAX; i++) Threads[i].activeSplitPoints = 0; @@ -603,12 +602,18 @@ void init_threads() { for (i = 1; i < THREAD_MAX; i++) { #if !defined(_MSC_VER) - pthread_create(pthread, NULL, init_thread, (void*)(&i)); + ok = (pthread_create(pthread, NULL, init_thread, (void*)(&i)) == 0); #else DWORD iID[1]; - CreateThread(NULL, 0, init_thread, (LPVOID)(&i), 0, iID); + ok = (CreateThread(NULL, 0, init_thread, (LPVOID)(&i), 0, iID) != NULL); #endif + if (!ok) + { + cout << "Failed to create thread number " << i << endl; + Application::exit_with_failure(); + } + // Wait until the thread has finished launching while (!Threads[i].running); } @@ -653,6 +658,7 @@ void SearchStack::init(int ply) { currentMove = threatMove = MOVE_NONE; reduction = Depth(0); eval = VALUE_NONE; + evalInfo = NULL; } void SearchStack::initKillers() { @@ -886,7 +892,7 @@ namespace { Value root_search(Position& pos, SearchStack ss[], RootMoveList& rml, Value alpha, Value beta) { Value oldAlpha = alpha; - Value value; + Value value = -VALUE_INFINITE; CheckInfo ci(pos); // Loop through all the moves in the root move list @@ -955,6 +961,8 @@ namespace { { // Try to reduce non-pv search depth by one ply if move seems not problematic, // if the move fails high will be re-searched at full depth. + bool doFullDepthSearch = true; + if ( depth >= 3*OnePly // FIXME was newDepth && !dangerous && !captureOrPromotion @@ -965,13 +973,11 @@ namespace { { ss[0].reduction = Depth(int(floor(red * int(OnePly)))); value = -search(pos, ss, -alpha, newDepth-ss[0].reduction, 1, true, 0); + doFullDepthSearch = (value > alpha); } - else - value = alpha + 1; // Just to trigger next condition - } else - value = alpha + 1; // Just to trigger next condition + } - if (value > alpha) + if (doFullDepthSearch) { value = -search(pos, ss, -alpha, newDepth, 1, true, 0); @@ -1105,7 +1111,7 @@ namespace { Value oldAlpha, value; bool isCheck, mateThreat, singleEvasion, moveIsCheck, captureOrPromotion, dangerous; int moveCount = 0; - Value bestValue = -VALUE_INFINITE; + Value bestValue = value = -VALUE_INFINITE; if (depth < OnePly) return qsearch(pos, ss, alpha, beta, Depth(0), ply, threadID); @@ -1207,6 +1213,8 @@ namespace { { // Try to reduce non-pv search depth by one ply if move seems not problematic, // if the move fails high will be re-searched at full depth. + bool doFullDepthSearch = true; + if ( depth >= 3*OnePly && !dangerous && !captureOrPromotion @@ -1218,14 +1226,11 @@ namespace { { ss[ply].reduction = Depth(int(floor(red * int(OnePly)))); value = -search(pos, ss, -alpha, newDepth-ss[ply].reduction, ply+1, true, threadID); + doFullDepthSearch = (value > alpha); } - else - value = alpha + 1; // Just to trigger next condition } - else - value = alpha + 1; // Just to trigger next condition - if (value > alpha) // Go with full depth non-pv search + if (doFullDepthSearch) // Go with full depth non-pv search { ss[ply].reduction = Depth(0); value = -search(pos, ss, -alpha, newDepth, ply+1, true, threadID); @@ -1332,7 +1337,7 @@ namespace { bool isCheck, useFutilityPruning, singleEvasion, moveIsCheck, captureOrPromotion, dangerous; bool mateThreat = false; int moveCount = 0; - futilityValue = staticValue = bestValue = -VALUE_INFINITE; + futilityValue = staticValue = bestValue = value = -VALUE_INFINITE; if (depth < OnePly) return qsearch(pos, ss, beta-1, beta, Depth(0), ply, threadID); @@ -1376,14 +1381,15 @@ namespace { const int FutilityValueMargin = 112 * bitScanReverse32(int(depth) * int(depth) / 2); // Evaluate the position statically - if (isCheck) - ss[ply].eval = VALUE_NONE; - else + if (!isCheck) { if (tte && (tte->type() & VALUE_TYPE_EVAL)) staticValue = value_from_tt(tte->value(), ply); else + { staticValue = evaluate(pos, ei, threadID); + ss[ply].evalInfo = &ei; + } ss[ply].eval = staticValue; futilityValue = staticValue + FutilityValueMargin; @@ -1456,7 +1462,7 @@ namespace { // Go with internal iterative deepening if we don't have a TT move if (UseIIDAtNonPVNodes && ttMove == MOVE_NONE && depth >= 8*OnePly && - !isCheck && evaluate(pos, ei, threadID) >= beta - IIDMargin) + !isCheck && ss[ply].eval >= beta - IIDMargin) { search(pos, ss, beta, Min(depth/2, depth-2*OnePly), ply, false, threadID); ttMove = ss[ply].pv[ply]; @@ -1541,6 +1547,8 @@ namespace { // Try to reduce non-pv search depth by one ply if move seems not problematic, // if the move fails high will be re-searched at full depth. + bool doFullDepthSearch = true; + if ( depth >= 3*OnePly && !dangerous && !captureOrPromotion @@ -1553,14 +1561,11 @@ namespace { { ss[ply].reduction = Depth(int(floor(red * int(OnePly)))); value = -search(pos, ss, -(beta-1), newDepth-ss[ply].reduction, ply+1, true, threadID); + doFullDepthSearch = (value >= beta); } - else - value = beta; // Just to trigger next condition } - else - value = beta; // Just to trigger next condition - if (value >= beta) // Go with full depth non-pv search + if (doFullDepthSearch) // Go with full depth non-pv search { ss[ply].reduction = Depth(0); value = -search(pos, ss, -(beta-1), newDepth, ply+1, true, threadID); @@ -1641,7 +1646,7 @@ namespace { StateInfo st; Move ttMove, move; Value staticValue, bestValue, value, futilityBase, futilityValue; - bool isCheck, enoughMaterial, moveIsCheck; + bool isCheck, enoughMaterial, moveIsCheck, evasionPrunable; const TTEntry* tte = NULL; int moveCount = 0; bool pvNode = (beta - alpha != 1); @@ -1696,10 +1701,14 @@ namespace { if (bestValue > alpha) alpha = bestValue; + // If we are near beta then try to get a cutoff pushing checks a bit further + bool deepChecks = depth == -OnePly && staticValue >= beta - PawnValueMidgame / 8; + // Initialize a MovePicker object for the current position, and prepare - // to search the moves. Because the depth is <= 0 here, only captures, - // queen promotions and checks (only if depth == 0) will be generated. - MovePicker mp = MovePicker(pos, ttMove, depth, H); + // to search the moves. Because the depth is <= 0 here, only captures, + // queen promotions and checks (only if depth == 0 or depth == -OnePly + // and we are near beta) will be generated. + MovePicker mp = MovePicker(pos, ttMove, deepChecks ? Depth(0) : depth, H); CheckInfo ci(pos); enoughMaterial = pos.non_pawn_material(pos.side_to_move()) > RookValueMidgame; futilityBase = staticValue + FutilityMarginQS + ei.futilityMargin; @@ -1738,8 +1747,15 @@ namespace { } } - // Don't search captures and checks with negative SEE values - if ( !isCheck + // Detect blocking evasions that are candidate to be pruned + evasionPrunable = isCheck + && bestValue != -VALUE_INFINITE + && !pos.move_is_capture(move) + && pos.type_of_piece_on(move_from(move)) != KING + && !pos.can_castle(pos.side_to_move()); + + // Don't search moves with negative SEE values + if ( (!isCheck || evasionPrunable) && move != ttMove && !move_is_promotion(move) && pos.see_sign(move) < 0) @@ -1807,17 +1823,16 @@ namespace { assert(threadID >= 0 && threadID < ActiveThreads); assert(ActiveThreads > 1); - Position pos = Position(sp->pos); + Position pos(*sp->pos); CheckInfo ci(pos); SearchStack* ss = sp->sstack[threadID]; - Value value; + Value value = -VALUE_INFINITE; Move move; bool isCheck = pos.is_check(); bool useFutilityPruning = sp->depth < SelectiveDepth && !isCheck; const int FutilityMoveCountMargin = 3 + (1 << (3 * int(sp->depth) / 8)); - const int FutilityValueMargin = 112 * bitScanReverse32(int(sp->depth) * int(sp->depth) / 2); while ( sp->bestValue < sp->beta && !thread_should_stop(threadID) @@ -1851,12 +1866,6 @@ namespace { continue; // Value based pruning - if (sp->futilityValue == VALUE_NONE) - { - EvalInfo ei; - sp->futilityValue = evaluate(pos, ei, threadID) + FutilityValueMargin; - } - Value futilityValueScaled = sp->futilityValue - moveCount * IncrementalFutilityMargin; if (futilityValueScaled < sp->beta) @@ -1878,6 +1887,8 @@ namespace { // Try to reduce non-pv search depth by one ply if move seems not problematic, // if the move fails high will be re-searched at full depth. + bool doFullDepthSearch = true; + if ( !dangerous && !captureOrPromotion && !move_is_castle(move) @@ -1888,14 +1899,11 @@ namespace { { ss[sp->ply].reduction = Depth(int(floor(red * int(OnePly)))); value = -search(pos, ss, -(sp->beta-1), newDepth-ss[sp->ply].reduction, sp->ply+1, true, threadID); + doFullDepthSearch = (value >= sp->beta); } - else - value = sp->beta; // Just to trigger next condition } - else - value = sp->beta; // Just to trigger next condition - if (value >= sp->beta) // Go with full depth non-pv search + if (doFullDepthSearch) // Go with full depth non-pv search { ss[sp->ply].reduction = Depth(0); value = -search(pos, ss, -(sp->beta - 1), newDepth, sp->ply+1, true, threadID); @@ -1957,10 +1965,10 @@ namespace { assert(threadID >= 0 && threadID < ActiveThreads); assert(ActiveThreads > 1); - Position pos = Position(sp->pos); + Position pos(*sp->pos); CheckInfo ci(pos); SearchStack* ss = sp->sstack[threadID]; - Value value; + Value value = -VALUE_INFINITE; Move move; while ( sp->alpha < sp->beta @@ -1989,6 +1997,8 @@ namespace { // Try to reduce non-pv search depth by one ply if move seems not problematic, // if the move fails high will be re-searched at full depth. + bool doFullDepthSearch = true; + if ( !dangerous && !captureOrPromotion && !move_is_castle(move) @@ -1997,31 +2007,37 @@ namespace { double red = 0.5 + ln(moveCount) * ln(sp->depth / 2) / 6.0; if (red >= 1.0) { + Value localAlpha = sp->alpha; ss[sp->ply].reduction = Depth(int(floor(red * int(OnePly)))); - value = -search(pos, ss, -sp->alpha, newDepth-ss[sp->ply].reduction, sp->ply+1, true, threadID); + value = -search(pos, ss, -localAlpha, newDepth-ss[sp->ply].reduction, sp->ply+1, true, threadID); + doFullDepthSearch = (value > localAlpha); } - else - value = sp->alpha + 1; // Just to trigger next condition } - else - value = sp->alpha + 1; // Just to trigger next condition - if (value > sp->alpha) // Go with full depth non-pv search + if (doFullDepthSearch) // Go with full depth non-pv search { + Value localAlpha = sp->alpha; ss[sp->ply].reduction = Depth(0); - value = -search(pos, ss, -sp->alpha, newDepth, sp->ply+1, true, threadID); + value = -search(pos, ss, -localAlpha, newDepth, sp->ply+1, true, threadID); - if (value > sp->alpha && value < sp->beta) + if (value > localAlpha && value < sp->beta) { // When the search fails high at ply 1 while searching the first - // move at the root, set the flag failHighPly1. This is used for + // move at the root, set the flag failHighPly1. This is used for // time managment: We don't want to stop the search early in // such cases, because resolving the fail high at ply 1 could // result in a big drop in score at the root. if (sp->ply == 1 && RootMoveNumber == 1) Threads[threadID].failHighPly1 = true; - value = -search_pv(pos, ss, -sp->beta, -sp->alpha, newDepth, sp->ply+1, threadID); + // If another thread has failed high then sp->alpha has been increased + // to be higher or equal then beta, if so, avoid to start a PV search. + localAlpha = sp->alpha; + if (localAlpha < sp->beta) + value = -search_pv(pos, ss, -sp->beta, -localAlpha, newDepth, sp->ply+1, threadID); + else + assert(thread_should_stop(threadID)); + Threads[threadID].failHighPly1 = false; } } @@ -2033,35 +2049,40 @@ namespace { break; // New best move? - lock_grab(&(sp->lock)); - if (value > sp->bestValue && !thread_should_stop(threadID)) + if (value > sp->bestValue) // Less then 2% of cases { - sp->bestValue = value; - if (value > sp->alpha) + lock_grab(&(sp->lock)); + if (value > sp->bestValue && !thread_should_stop(threadID)) { - sp->alpha = value; - sp_update_pv(sp->parentSstack, ss, sp->ply); - if (value == value_mate_in(sp->ply + 1)) - ss[sp->ply].mateKiller = move; - - if (value >= sp->beta) + sp->bestValue = value; + if (value > sp->alpha) { - for (int i = 0; i < ActiveThreads; i++) - if (i != threadID && (i == sp->master || sp->slaves[i])) - Threads[i].stop = true; + // Ask threads to stop before to modify sp->alpha + if (value >= sp->beta) + { + for (int i = 0; i < ActiveThreads; i++) + if (i != threadID && (i == sp->master || sp->slaves[i])) + Threads[i].stop = true; - sp->finished = true; + sp->finished = true; + } + + sp->alpha = value; + + sp_update_pv(sp->parentSstack, ss, sp->ply); + if (value == value_mate_in(sp->ply + 1)) + ss[sp->ply].mateKiller = move; } - } - // If we are at ply 1, and we are searching the first root move at - // ply 0, set the 'Problem' variable if the score has dropped a lot - // (from the computer's point of view) since the previous iteration. - if ( sp->ply == 1 - && Iteration >= 2 - && -value <= IterationInfo[Iteration-1].value - ProblemMargin) - Problem = true; + // If we are at ply 1, and we are searching the first root move at + // ply 0, set the 'Problem' variable if the score has dropped a lot + // (from the computer's point of view) since the previous iteration. + if ( sp->ply == 1 + && Iteration >= 2 + && -value <= IterationInfo[Iteration-1].value - ProblemMargin) + Problem = true; + } + lock_release(&(sp->lock)); } - lock_release(&(sp->lock)); } lock_grab(&(sp->lock)); @@ -2795,6 +2816,8 @@ namespace { // If this thread has been assigned work, launch a search if (Threads[threadID].workIsWaiting) { + assert(!Threads[threadID].idle); + Threads[threadID].workIsWaiting = false; if (Threads[threadID].splitPoint->pvNode) sp_search_pv(Threads[threadID].splitPoint, threadID); @@ -2881,7 +2904,10 @@ namespace { if (!Threads[slave].idle || slave == master) return false; - if (Threads[slave].activeSplitPoints == 0) + // Make a local copy to be sure doesn't change under our feet + int localActiveSplitPoints = Threads[slave].activeSplitPoints; + + if (localActiveSplitPoints == 0) // No active split points means that the thread is available as // a slave for any other thread. return true; @@ -2889,8 +2915,10 @@ namespace { if (ActiveThreads == 2) return true; - // Apply the "helpful master" concept if possible - if (SplitPointStack[slave][Threads[slave].activeSplitPoints - 1].slaves[master]) + // Apply the "helpful master" concept if possible. Use localActiveSplitPoints + // that is known to be > 0, instead of Threads[slave].activeSplitPoints that + // could have been set to 0 by another thread leading to an out of bound access. + if (SplitPointStack[slave][localActiveSplitPoints - 1].slaves[master]) return true; return false; @@ -2940,7 +2968,6 @@ namespace { assert(ActiveThreads > 1); SplitPoint* splitPoint; - int i; lock_grab(&MPLock); @@ -2957,7 +2984,7 @@ namespace { splitPoint = SplitPointStack[master] + Threads[master].activeSplitPoints; Threads[master].activeSplitPoints++; - // Initialize the split point object and copy current position + // Initialize the split point object splitPoint->parent = Threads[master].splitPoint; splitPoint->finished = false; splitPoint->ply = ply; @@ -2971,37 +2998,40 @@ namespace { splitPoint->mp = mp; splitPoint->moves = *moves; splitPoint->cpus = 1; - splitPoint->pos.copy(p); + splitPoint->pos = &p; splitPoint->parentSstack = sstck; - for (i = 0; i < ActiveThreads; i++) + for (int i = 0; i < ActiveThreads; i++) splitPoint->slaves[i] = 0; - // Copy the current search stack to the master thread - memcpy(splitPoint->sstack[master], sstck, (ply+1) * sizeof(SearchStack)); + Threads[master].idle = false; + Threads[master].stop = false; Threads[master].splitPoint = splitPoint; - // Make copies of the current position and search stack for each thread - for (i = 0; i < ActiveThreads && splitPoint->cpus < MaxThreadsPerSplitPoint; i++) + // Allocate available threads setting idle flag to false + for (int i = 0; i < ActiveThreads && splitPoint->cpus < MaxThreadsPerSplitPoint; i++) if (thread_is_available(i, master)) { - memcpy(splitPoint->sstack[i], sstck, (ply+1) * sizeof(SearchStack)); + Threads[i].idle = false; + Threads[i].stop = false; Threads[i].splitPoint = splitPoint; splitPoint->slaves[i] = 1; splitPoint->cpus++; } + assert(splitPoint->cpus > 1); + + // We can release the lock because master and slave threads are already booked + lock_release(&MPLock); + // Tell the threads that they have work to do. This will make them leave - // their idle loop. - for (i = 0; i < ActiveThreads; i++) + // their idle loop. But before copy search stack tail for each thread. + for (int i = 0; i < ActiveThreads; i++) if (i == master || splitPoint->slaves[i]) { - Threads[i].workIsWaiting = true; - Threads[i].idle = false; - Threads[i].stop = false; + memcpy(splitPoint->sstack[i] + ply - 1, sstck + ply - 1, 3 * sizeof(SearchStack)); + Threads[i].workIsWaiting = true; // This makes the slave to exit from idle_loop() } - lock_release(&MPLock); - // Everything is set up. The master thread enters the idle loop, from // which it will instantly launch a search, because its workIsWaiting // slot is 'true'. We send the split point as a second parameter to the