X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fsearch.cpp;h=dfe18bc88599f75e8340fbf5163958681b88a16b;hp=f24f33b91cfb48d63eb1ec60e81c7d90caff663b;hb=f7096ea7cedeb81d9799e4440a670736825c6a7e;hpb=62d38f0196b077a6f67213f5fbbbb28eb60e7d92 diff --git a/src/search.cpp b/src/search.cpp index f24f33b9..dfe18bc8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -66,7 +66,7 @@ namespace { // Futility lookup tables (initialized at startup) and their access functions Value FutilityMargins[16][64]; // [depth][moveNumber] - int FutilityMoveCounts[32]; // [depth] + int FutilityMoveCounts[2][32]; // [improving][depth] inline Value futility_margin(Depth d, int mn) { @@ -100,6 +100,7 @@ namespace { Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply); bool check_is_dangerous(const Position& pos, Move move, Value futilityBase, Value beta); + bool allows(const Position& pos, Move first, Move second); bool refutes(const Position& pos, Move first, Move second); string uci_pv(const Position& pos, int depth, Value alpha, Value beta); @@ -145,14 +146,18 @@ void Search::init() { // Init futility move count array for (d = 0; d < 32; d++) - FutilityMoveCounts[d] = int(3.001 + 0.3 * pow(double(d), 1.8)); + { + FutilityMoveCounts[1][d] = int(3.001 + 0.3 * pow(double(d), 1.8)); + FutilityMoveCounts[0][d] = d < 5 ? FutilityMoveCounts[1][d] + : 3 * FutilityMoveCounts[1][d] / 4; + } } /// Search::perft() is our utility to verify move generation. All the leaf nodes /// up to the given depth are generated and counted and the sum returned. -size_t Search::perft(Position& pos, Depth depth) { +static size_t perft(Position& pos, Depth depth) { StateInfo st; size_t cnt = 0; @@ -162,12 +167,15 @@ size_t Search::perft(Position& pos, Depth depth) { for (MoveList it(pos); *it; ++it) { pos.do_move(*it, st, ci, pos.move_gives_check(*it, ci)); - cnt += leaf ? MoveList(pos).size() : perft(pos, depth - ONE_PLY); + cnt += leaf ? MoveList(pos).size() : ::perft(pos, depth - ONE_PLY); pos.undo_move(*it); } return cnt; } +size_t Search::perft(Position& pos, Depth depth) { + return depth > ONE_PLY ? ::perft(pos, depth) : MoveList(pos).size(); +} /// Search::think() is the external interface to Stockfish's search, and is /// called by the main thread when the program receives the UCI 'go' command. It @@ -211,7 +219,7 @@ void Search::think() { else DrawValue[WHITE] = DrawValue[BLACK] = VALUE_DRAW; - if (Options["Use Search Log"]) + if (Options["Write Search Log"]) { Log log(Options["Search Log Filename"]); log << "\nSearching: " << RootPos.fen() @@ -227,7 +235,7 @@ void Search::think() { for (size_t i = 0; i < Threads.size(); i++) Threads[i]->maxPly = 0; - Threads.sleepWhileIdle = Options["Use Sleeping Threads"]; + Threads.sleepWhileIdle = Options["Idle Threads Sleep"]; // Set best timer interval to avoid lagging under time pressure. Timer is // used to check for remaining available thinking time. @@ -243,7 +251,7 @@ void Search::think() { Threads.timer->msec = 0; // Stop the timer Threads.sleepWhileIdle = true; // Send idle threads to sleep - if (Options["Use Search Log"]) + if (Options["Write Search Log"]) { Time::point elapsed = Time::now() - SearchTime + 1; @@ -290,11 +298,11 @@ namespace { void id_loop(Position& pos) { - Stack stack[MAX_PLY_PLUS_2], *ss = stack+1; // To allow referencing (ss-1) + Stack stack[MAX_PLY_PLUS_3], *ss = stack+2; // To allow referencing (ss-2) int depth, prevBestMoveChanges; Value bestValue, alpha, beta, delta; - memset(ss-1, 0, 4 * sizeof(Stack)); + std::memset(ss-2, 0, 5 * sizeof(Stack)); (ss-1)->currentMove = MOVE_NULL; // Hack to skip update gains depth = BestMoveChanges = 0; @@ -363,6 +371,12 @@ namespace { if (Signals.stop) return; + // When failing high/low give some update (without cluttering + // the UI) before to research. + if ( (bestValue <= alpha || bestValue >= beta) + && Time::now() - SearchTime > 3000) + sync_cout << uci_pv(pos, depth, alpha, beta) << sync_endl; + // In case of failing low/high increase aspiration window and // research, otherwise exit the loop. if (bestValue <= alpha) @@ -381,10 +395,6 @@ namespace { delta += delta / 2; assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); - - // Give some update (without cluttering the UI) before to research - if (Time::now() - SearchTime > 3000) - sync_cout << uci_pv(pos, depth, alpha, beta) << sync_endl; } // Sort the PV lines searched so far and update the GUI @@ -398,7 +408,7 @@ namespace { if (skill.enabled() && skill.time_to_pick(depth)) skill.pick_move(); - if (Options["Use Search Log"]) + if (Options["Write Search Log"]) { RootMove& rm = RootMoves[0]; if (skill.best != MOVE_NONE) @@ -490,13 +500,12 @@ namespace { Depth ext, newDepth; Value bestValue, value, ttValue; Value eval, nullValue, futilityValue; - bool inCheck, givesCheck, pvMove, singularExtensionNode; + bool inCheck, givesCheck, pvMove, singularExtensionNode, improving; bool captureOrPromotion, dangerous, doFullDepthSearch; int moveCount, quietCount; // Step 1. Initialize node Thread* thisThread = pos.this_thread(); - moveCount = quietCount = 0; inCheck = pos.checkers(); if (SpNode) @@ -511,9 +520,10 @@ namespace { assert(splitPoint->bestValue > -VALUE_INFINITE && splitPoint->moveCount > 0); - goto split_point_start; + goto moves_loop; } + moveCount = quietCount = 0; bestValue = -VALUE_INFINITE; ss->currentMove = threatMove = (ss+1)->excludedMove = bestMove = MOVE_NONE; ss->ply = (ss-1)->ply + 1; @@ -582,7 +592,7 @@ namespace { if (inCheck) { ss->staticEval = ss->evalMargin = eval = VALUE_NONE; - goto iid_start; + goto moves_loop; } else if (tte) @@ -617,7 +627,7 @@ namespace { Gains.update(pos.piece_on(to), to, -(ss-1)->staticEval - ss->staticEval); } - // Step 6. Razoring (is omitted in PV nodes) + // Step 6. Razoring (skipped when in check) if ( !PvNode && depth < 4 * ONE_PLY && eval + razor_margin(depth) < beta @@ -633,7 +643,7 @@ namespace { return v; } - // Step 7. Static null move pruning (is omitted in PV nodes) + // Step 7. Static null move pruning (skipped when in check) // We're betting that the opponent doesn't have a move that will reduce // the score by more than futility_margin(depth) if we do a null move. if ( !PvNode @@ -689,19 +699,22 @@ namespace { else { // The null move failed low, which means that we may be faced with - // some kind of threat. If the previous move was reduced return a fail + // some kind of threat. If the previous move was reduced, check if + // the move that refuted the null move was somehow connected to the + // move which was reduced. If a connection is found, return a fail // low score (which will cause the reduced move to fail high in the // parent node, which will trigger a re-search with full depth). + threatMove = (ss+1)->currentMove; + if ( depth < 5 * ONE_PLY && (ss-1)->reduction - && nullValue < beta - Value(128)) + && threatMove != MOVE_NONE + && allows(pos, (ss-1)->currentMove, threatMove)) return alpha; - - threatMove = (ss+1)->currentMove; } } - // Step 9. ProbCut (is omitted in PV nodes) + // Step 9. ProbCut (skipped when in check) // If we have a very good capture (i.e. SEE > seeValues[captured_piece_type]) // and a reduced search returns a value much above beta, we can (almost) safely // prune the previous move. @@ -732,12 +745,10 @@ namespace { } } -iid_start: // When in check we skip early cut tests - - // Step 10. Internal iterative deepening + // Step 10. Internal iterative deepening (skipped when in check) if ( depth >= (PvNode ? 5 * ONE_PLY : 8 * ONE_PLY) && ttMove == MOVE_NONE - && (PvNode || (!inCheck && ss->staticEval + Value(256) >= beta))) + && (PvNode || ss->staticEval + Value(256) >= beta)) { Depth d = depth - 2 * ONE_PLY - (PvNode ? DEPTH_ZERO : depth / 4); @@ -749,15 +760,16 @@ iid_start: // When in check we skip early cut tests ttMove = tte ? tte->move() : MOVE_NONE; } -split_point_start: // At split points actual search starts from here +moves_loop: // When in check and at SpNode search starts from here Square prevMoveSq = to_sq((ss-1)->currentMove); Move countermoves[] = { Countermoves[pos.piece_on(prevMoveSq)][prevMoveSq].first, Countermoves[pos.piece_on(prevMoveSq)][prevMoveSq].second }; - MovePicker mp(pos, ttMove, depth, History, countermoves, ss, PvNode ? -VALUE_INFINITE : beta); + MovePicker mp(pos, ttMove, depth, History, countermoves, ss); CheckInfo ci(pos); value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc + improving = ss->staticEval >= (ss-2)->staticEval; singularExtensionNode = !RootNode && !SpNode && depth >= (PvNode ? 6 * ONE_PLY : 8 * ONE_PLY) @@ -797,7 +809,7 @@ split_point_start: // At split points actual search starts from here { Signals.firstRootMove = (moveCount == 1); - if (thisThread == Threads.main_thread() && Time::now() - SearchTime > 3000) + if (thisThread == Threads.main() && Time::now() - SearchTime > 3000) sync_cout << "info depth " << depth / ONE_PLY << " currmove " << move_to_uci(move, pos.is_chess960()) << " currmovenumber " << moveCount + PVIdx << sync_endl; @@ -854,7 +866,7 @@ split_point_start: // At split points actual search starts from here { // Move count based pruning if ( depth < 16 * ONE_PLY - && moveCount >= FutilityMoveCounts[depth] + && moveCount >= FutilityMoveCounts[improving][depth] && (!threatMove || !refutes(pos, move, threatMove))) { if (SpNode) @@ -1119,7 +1131,7 @@ split_point_start: // At split points actual search starts from here Key posKey; Move ttMove, move, bestMove; Value bestValue, value, ttValue, futilityValue, futilityBase, oldAlpha; - bool givesCheck, enoughMaterial, evasionPrunable; + bool givesCheck, evasionPrunable; Depth ttDepth; // To flag BOUND_EXACT a node with eval above alpha and no available moves @@ -1161,7 +1173,6 @@ split_point_start: // At split points actual search starts from here { ss->staticEval = ss->evalMargin = VALUE_NONE; bestValue = futilityBase = -VALUE_INFINITE; - enoughMaterial = false; } else { @@ -1189,7 +1200,6 @@ split_point_start: // At split points actual search starts from here alpha = bestValue; futilityBase = ss->staticEval + ss->evalMargin + Value(128); - enoughMaterial = pos.non_pawn_material(pos.side_to_move()) > RookValueMg; } // Initialize a MovePicker object for the current position, and prepare @@ -1211,7 +1221,6 @@ split_point_start: // At split points actual search starts from here && !InCheck && !givesCheck && move != ttMove - && enoughMaterial && type_of(move) != PROMOTION && !pos.is_passed_pawn_push(move)) { @@ -1236,8 +1245,7 @@ split_point_start: // At split points actual search starts from here } // Detect non-capture evasions that are candidate to be pruned - evasionPrunable = !PvNode - && InCheck + evasionPrunable = InCheck && bestValue > VALUE_MATED_IN_MAX_PLY && !pos.is_capture(move) && !pos.can_castle(pos.side_to_move()); @@ -1373,6 +1381,47 @@ split_point_start: // At split points actual search starts from here } + // allows() tests whether the 'first' move at previous ply somehow makes the + // 'second' move possible, for instance if the moving piece is the same in + // both moves. Normally the second move is the threat (the best move returned + // from a null search that fails low). + + bool allows(const Position& pos, Move first, Move second) { + + assert(is_ok(first)); + assert(is_ok(second)); + assert(color_of(pos.piece_on(from_sq(second))) == ~pos.side_to_move()); + assert(color_of(pos.piece_on(to_sq(first))) == ~pos.side_to_move()); + + Square m1from = from_sq(first); + Square m2from = from_sq(second); + Square m1to = to_sq(first); + Square m2to = to_sq(second); + + // The piece is the same or second's destination was vacated by the first move + if (m1to == m2from || m2to == m1from) + return true; + + // Second one moves through the square vacated by first one + if (between_bb(m2from, m2to) & m1from) + return true; + + // Second's destination is defended by the first move's piece + Bitboard m1att = pos.attacks_from(pos.piece_on(m1to), m1to, pos.pieces() ^ m2from); + if (m1att & m2to) + return true; + + // Second move gives a discovered check through the first's checking piece + if (m1att & pos.king_square(pos.side_to_move())) + { + assert(between_bb(m1to, pos.king_square(pos.side_to_move())) & m2from); + return true; + } + + return false; + } + + // refutes() tests whether a 'first' move is able to defend against a 'second' // opponent's move. In this case will not be pruned. Normally the second move // is the threat (the best move returned from a null search that fails low). @@ -1410,7 +1459,7 @@ split_point_start: // At split points actual search starts from here | (attacks_bb(m2to, occ) & pos.pieces(color_of(pc), QUEEN, BISHOP)); // Verify attackers are triggered by our move and not already existing - if (xray && (xray ^ (xray & pos.attacks_from(m2to)))) + if (unlikely(xray) && (xray & ~pos.attacks_from(m2to))) return true; } @@ -1518,7 +1567,7 @@ split_point_start: // At split points actual search starts from here void RootMove::extract_pv_from_tt(Position& pos) { - StateInfo state[MAX_PLY_PLUS_2], *st = state; + StateInfo state[MAX_PLY_PLUS_3], *st = state; const TTEntry* tte; int ply = 0; Move m = pv[0]; @@ -1551,7 +1600,7 @@ void RootMove::extract_pv_from_tt(Position& pos) { void RootMove::insert_pv_in_tt(Position& pos) { - StateInfo state[MAX_PLY_PLUS_2], *st = state; + StateInfo state[MAX_PLY_PLUS_3], *st = state; const TTEntry* tte; int ply = 0; @@ -1621,14 +1670,15 @@ void Thread::idle_loop() { Threads.mutex.lock(); assert(searching); + assert(activeSplitPoint); SplitPoint* sp = activeSplitPoint; Threads.mutex.unlock(); - Stack stack[MAX_PLY_PLUS_2], *ss = stack+1; // To allow referencing (ss-1) + Stack stack[MAX_PLY_PLUS_3], *ss = stack+2; // To allow referencing (ss-2) Position pos(*sp->pos, this); - memcpy(ss-1, sp->ss-1, 4 * sizeof(Stack)); + std::memcpy(ss-2, sp->ss-2, 5 * sizeof(Stack)); ss->splitPoint = sp; sp->mutex.lock();