From 7eaea3848c9e8a388c0b79cee6fba6bf3cd32108 Mon Sep 17 00:00:00 2001 From: Marco Costalba Date: Mon, 11 Apr 2016 16:45:36 +0200 Subject: [PATCH 1/6] StateInfo is usually allocated on the stack by search() And passed in do_move(), this ensures maximum efficiency and speed and at the same time unlimited move numbers. The draw back is that to handle Position init we need to reserve a StateInfo inside Position itself and use at init time and when copying from another Position. After lazy SMP we don't need anymore this gimmick and we can get rid of this special case and always pass an external StateInfo to Position object. Also rewritten and simplified Position constructors. Verified it does not regress with a 3 threads SMP test: ELO: -0.00 +-12.7 (95%) LOS: 50.0% Total: 1000 W: 173 L: 173 D: 654 No functional change. --- src/benchmark.cpp | 7 ++++--- src/endgame.cpp | 3 ++- src/position.cpp | 45 +++++++++++------------------------------- src/position.h | 14 ++++++------- src/search.cpp | 13 ++---------- src/search.h | 11 +++-------- src/syzygy/tbprobe.cpp | 4 ++-- src/syzygy/tbprobe.h | 4 ++-- src/thread.cpp | 36 +++++++++++++++++++-------------- src/thread.h | 7 +++++-- src/timeman.cpp | 2 +- src/uci.cpp | 18 +++++++++-------- 12 files changed, 70 insertions(+), 94 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 6d69541f..2978f346 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -144,10 +144,12 @@ void benchmark(const Position& current, istream& is) { uint64_t nodes = 0; TimePoint elapsed = now(); + Position pos; for (size_t i = 0; i < fens.size(); ++i) { - Position pos(fens[i], Options["UCI_Chess960"], Threads.main()); + StateListPtr states(new std::vector(1)); + pos.set(fens[i], Options["UCI_Chess960"], &states->back(), Threads.main()); cerr << "\nPosition: " << i + 1 << '/' << fens.size() << endl; @@ -156,9 +158,8 @@ void benchmark(const Position& current, istream& is) { else { - Search::StateStackPtr st; limits.startTime = now(); - Threads.start_thinking(pos, limits, st); + Threads.start_thinking(pos, states, limits); Threads.main()->wait_for_search_finished(); nodes += Threads.nodes_searched(); } diff --git a/src/endgame.cpp b/src/endgame.cpp index cc663043..04469e87 100644 --- a/src/endgame.cpp +++ b/src/endgame.cpp @@ -99,7 +99,8 @@ namespace { string fen = sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/8/8/" + sides[1] + char(8 - sides[1].length() + '0') + " w - - 0 10"; - return Position(fen, false, nullptr).material_key(); + StateInfo st; + return Position().set(fen, false, &st, nullptr).material_key(); } } // namespace diff --git a/src/position.cpp b/src/position.cpp index f42377de..6e8b03d1 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -155,42 +155,11 @@ void Position::init() { } -/// Position::operator=() creates a copy of 'pos' but detaching the state pointer -/// from the source to be self-consistent and not depending on any external data. - -Position& Position::operator=(const Position& pos) { - - std::memcpy(this, &pos, sizeof(Position)); - std::memcpy(&startState, st, sizeof(StateInfo)); - st = &startState; - nodes = 0; - - assert(pos_is_ok()); - - return *this; -} - - -/// Position::clear() erases the position object to a pristine state, with an -/// empty board, white to move, and no castling rights. - -void Position::clear() { - - std::memset(this, 0, sizeof(Position)); - startState.epSquare = SQ_NONE; - st = &startState; - - for (int i = 0; i < PIECE_TYPE_NB; ++i) - for (int j = 0; j < 16; ++j) - pieceList[WHITE][i][j] = pieceList[BLACK][i][j] = SQ_NONE; -} - - /// Position::set() initializes the position object with the given FEN string. /// This function is not very robust - make sure that input FENs are correct, /// this is assumed to be the responsibility of the GUI. -void Position::set(const string& fenStr, bool isChess960, Thread* th) { +Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) { /* A FEN string defines a particular position using only the ASCII character set. @@ -230,7 +199,11 @@ void Position::set(const string& fenStr, bool isChess960, Thread* th) { Square sq = SQ_A8; std::istringstream ss(fenStr); - clear(); + std::memset(this, 0, sizeof(Position)); + std::memset(si, 0, sizeof(StateInfo)); + std::fill_n(&pieceList[0][0][0], sizeof(pieceList) / sizeof(Square), SQ_NONE); + st = si; + ss >> std::noskipws; // 1. Piece placement @@ -291,6 +264,8 @@ void Position::set(const string& fenStr, bool isChess960, Thread* th) { if (!(attackers_to(st->epSquare) & pieces(sideToMove, PAWN))) st->epSquare = SQ_NONE; } + else + st->epSquare = SQ_NONE; // 5-6. Halfmove clock and fullmove number ss >> std::skipws >> st->rule50 >> gamePly; @@ -304,6 +279,8 @@ void Position::set(const string& fenStr, bool isChess960, Thread* th) { set_state(st); assert(pos_is_ok()); + + return *this; } @@ -1108,7 +1085,7 @@ void Position::flip() { std::getline(ss, token); // Half and full moves f += token; - set(f, is_chess960(), this_thread()); + set(f, is_chess960(), st, this_thread()); assert(pos_is_ok()); } diff --git a/src/position.h b/src/position.h index 1070236b..d44ed009 100644 --- a/src/position.h +++ b/src/position.h @@ -23,7 +23,9 @@ #include #include // For offsetof() +#include // For std::unique_ptr #include +#include #include "bitboard.h" #include "types.h" @@ -75,6 +77,8 @@ struct StateInfo { StateInfo* previous; }; +typedef std::unique_ptr> StateListPtr; + /// Position class stores information regarding the board representation as /// pieces, side to move, hash keys, castling info, etc. Important methods are @@ -86,14 +90,12 @@ class Position { public: static void init(); - Position() = default; // To define the global object RootPos + Position() = default; Position(const Position&) = delete; - Position(const Position& pos, Thread* th) { *this = pos; thisThread = th; } - Position(const std::string& f, bool c960, Thread* th) { set(f, c960, th); } - Position& operator=(const Position&); // To assign RootPos from UCI + Position& operator=(const Position&) = delete; // FEN string input/output - void set(const std::string& fenStr, bool isChess960, Thread* th); + Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th); const std::string fen() const; // Position representation @@ -178,7 +180,6 @@ public: private: // Initialization helpers (used while setting up a position) - void clear(); void set_castling_right(Color c, Square rfrom); void set_state(StateInfo* si) const; @@ -200,7 +201,6 @@ private: int castlingRightsMask[SQUARE_NB]; Square castlingRookSquare[CASTLING_RIGHT_NB]; Bitboard castlingPath[CASTLING_RIGHT_NB]; - StateInfo startState; uint64_t nodes; int gamePly; Color sideToMove; diff --git a/src/search.cpp b/src/search.cpp index 10d491c0..31764533 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -40,7 +40,6 @@ namespace Search { SignalsType Signals; LimitsType Limits; - StateStackPtr SetupStates; } namespace Tablebases { @@ -317,16 +316,8 @@ void MainThread::search() { } for (Thread* th : Threads) - { - th->maxPly = 0; - th->rootDepth = DEPTH_ZERO; if (th != this) - { - th->rootPos = Position(rootPos, th); - th->rootMoves = rootMoves; th->start_searching(); - } - } Thread::search(); // Let's start searching! } @@ -1488,7 +1479,7 @@ moves_loop: // When in check search starts from here Move Skill::pick_best(size_t multiPV) { - const Search::RootMoveVector& rootMoves = Threads.main()->rootMoves; + const RootMoves& rootMoves = Threads.main()->rootMoves; static PRNG rng(now()); // PRNG sequence should be non-deterministic // RootMoves are already sorted by score in descending order @@ -1553,7 +1544,7 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { std::stringstream ss; int elapsed = Time.elapsed() + 1; - const Search::RootMoveVector& rootMoves = pos.this_thread()->rootMoves; + const RootMoves& rootMoves = pos.this_thread()->rootMoves; size_t PVIdx = pos.this_thread()->PVIdx; size_t multiPV = std::min((size_t)Options["MultiPV"], rootMoves.size()); uint64_t nodes_searched = Threads.nodes_searched(); diff --git a/src/search.h b/src/search.h index 84420373..d722af1e 100644 --- a/src/search.h +++ b/src/search.h @@ -22,8 +22,6 @@ #define SEARCH_H_INCLUDED #include -#include // For std::unique_ptr -#include #include #include "misc.h" @@ -65,7 +63,7 @@ struct RootMove { std::vector pv; }; -typedef std::vector RootMoveVector; +typedef std::vector RootMoves; /// LimitsType struct stores information sent by GUI about available time to /// search the current move, maximum depth/time, if we are in analysis mode or @@ -74,8 +72,8 @@ typedef std::vector RootMoveVector; struct LimitsType { LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC - nodes = time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movestogo = - depth = movetime = mate = infinite = ponder = 0; + nodes = time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = + npmsec = movestogo = depth = movetime = mate = infinite = ponder = 0; } bool use_time_management() const { @@ -95,11 +93,8 @@ struct SignalsType { std::atomic_bool stop, stopOnPonderhit; }; -typedef std::unique_ptr> StateStackPtr; - extern SignalsType Signals; extern LimitsType Limits; -extern StateStackPtr SetupStates; void init(); void clear(); diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 14d34e79..e07cd1bc 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -688,7 +688,7 @@ static Value wdl_to_Value[5] = { // // A return value false indicates that not all probes were successful and that // no moves were filtered out. -bool Tablebases::root_probe(Position& pos, Search::RootMoveVector& rootMoves, Value& score) +bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, Value& score) { int success; @@ -795,7 +795,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoveVector& rootMoves, Va // // A return value false indicates that not all probes were successful and that // no moves were filtered out. -bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoveVector& rootMoves, Value& score) +bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, Value& score) { int success; diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index 4233e1aa..2bcc0834 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -10,8 +10,8 @@ extern int MaxCardinality; void init(const std::string& path); int probe_wdl(Position& pos, int *success); int probe_dtz(Position& pos, int *success); -bool root_probe(Position& pos, Search::RootMoveVector& rootMoves, Value& score); -bool root_probe_wdl(Position& pos, Search::RootMoveVector& rootMoves, Value& score); +bool root_probe(Position& pos, Search::RootMoves& rootMoves, Value& score); +bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, Value& score); } diff --git a/src/thread.cpp b/src/thread.cpp index 440b8bf0..dc4ec05e 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -26,8 +26,6 @@ #include "thread.h" #include "uci.h" -using namespace Search; - ThreadPool Threads; // Global object /// Thread constructor launches the thread and then waits until it goes to sleep @@ -171,26 +169,34 @@ int64_t ThreadPool::nodes_searched() { /// ThreadPool::start_thinking() wakes up the main thread sleeping in idle_loop() /// and starts a new search, then returns immediately. -void ThreadPool::start_thinking(const Position& pos, const LimitsType& limits, - StateStackPtr& states) { +void ThreadPool::start_thinking(const Position& pos, StateListPtr& states, + const Search::LimitsType& limits) { main()->wait_for_search_finished(); - Signals.stopOnPonderhit = Signals.stop = false; - - main()->rootMoves.clear(); - main()->rootPos = pos; - Limits = limits; - if (states.get()) // If we don't set a new position, preserve current state - { - SetupStates = std::move(states); // Ownership transfer here - assert(!states.get()); - } + Search::Signals.stopOnPonderhit = Search::Signals.stop = false; + Search::Limits = limits; + Search::RootMoves rootMoves; for (const auto& m : MoveList(pos)) if ( limits.searchmoves.empty() || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) - main()->rootMoves.push_back(RootMove(m)); + rootMoves.push_back(Search::RootMove(m)); + + // After ownership transfer 'states' becomes empty, so if we stop the search + // and call 'go' again without setting a new position states.get() == NULL. + assert(states.get() || setupStates.get()); + + if (states.get()) + setupStates = std::move(states); // Ownership transfer, states is now empty + + for (Thread* th : Threads) + { + th->maxPly = 0; + th->rootDepth = DEPTH_ZERO; + th->rootMoves = rootMoves; + th->rootPos.set(pos.fen(), pos.is_chess960(), &setupStates->back(), th); + } main()->start_searching(); } diff --git a/src/thread.h b/src/thread.h index 43ddfbb7..0b112de9 100644 --- a/src/thread.h +++ b/src/thread.h @@ -64,7 +64,7 @@ public: int maxPly, callsCnt; Position rootPos; - Search::RootMoveVector rootMoves; + Search::RootMoves rootMoves; Depth rootDepth; HistoryStats history; MoveStats counterMoves; @@ -94,9 +94,12 @@ struct ThreadPool : public std::vector { void exit(); // be initialized and valid during the whole thread lifetime. MainThread* main() { return static_cast(at(0)); } - void start_thinking(const Position&, const Search::LimitsType&, Search::StateStackPtr&); + void start_thinking(const Position&, StateListPtr&, const Search::LimitsType&); void read_uci_options(); int64_t nodes_searched(); + +private: + StateListPtr setupStates; }; extern ThreadPool Threads; diff --git a/src/timeman.cpp b/src/timeman.cpp index bc9c2e5b..c7c19f47 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -33,7 +33,7 @@ namespace { enum TimeType { OptimumTime, MaxTime }; const int MoveHorizon = 50; // Plan time management at most this many moves ahead - const double MaxRatio = 7.09; // When in trouble, we can step over reserved time with this ratio + const double MaxRatio = 7.09; // When in trouble, we can step over reserved time with this ratio const double StealRatio = 0.35; // However we must not steal time from remaining moves over this ratio diff --git a/src/uci.cpp b/src/uci.cpp index 4d12ca40..cfd253b1 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -39,10 +39,10 @@ namespace { // FEN string of the initial position, normal chess const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; - // Stack to keep track of the position states along the setup moves (from the + // A list to keep track of the position states along the setup moves (from the // start position to the position just before the search starts). Needed by // 'draw by repetition' detection. - Search::StateStackPtr SetupStates; + StateListPtr States(new std::vector(1)); // position() is called when engine receives the "position" UCI command. @@ -68,14 +68,14 @@ namespace { else return; - pos.set(fen, Options["UCI_Chess960"], Threads.main()); - SetupStates = Search::StateStackPtr(new std::stack); + States = StateListPtr(new std::vector(1)); + pos.set(fen, Options["UCI_Chess960"], &States->back(), Threads.main()); // Parse move list (if any) while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE) { - SetupStates->push(StateInfo()); - pos.do_move(m, SetupStates->top(), pos.gives_check(m, CheckInfo(pos))); + States->push_back(StateInfo()); + pos.do_move(m, States->back(), pos.gives_check(m, CheckInfo(pos))); } } @@ -132,7 +132,7 @@ namespace { else if (token == "infinite") limits.infinite = 1; else if (token == "ponder") limits.ponder = 1; - Threads.start_thinking(pos, limits, SetupStates); + Threads.start_thinking(pos, States, limits); } } // namespace @@ -146,9 +146,11 @@ namespace { void UCI::loop(int argc, char* argv[]) { - Position pos(StartFEN, false, Threads.main()); // The root position + Position pos; string token, cmd; + pos.set(StartFEN, false, &States->back(), Threads.main()); + for (int i = 1; i < argc; ++i) cmd += std::string(argv[i]) + " "; From ec6aab01366ba1d2de27084d3cc7415a31aa5a24 Mon Sep 17 00:00:00 2001 From: loco-loco Date: Sun, 17 Apr 2016 15:14:07 +0100 Subject: [PATCH 2/6] Add a second level of follow-up moves STC: LLR: 2.95 (-2.94,2.94) [0.00,5.00] Total: 6438 W: 1229 L: 1077 D: 4132 LTC: LLR: 2.96 (-2.94,2.94) [0.00,5.00] Total: 4000 W: 605 L: 473 D: 2922 bench: 7378965 Resolves #636 --- src/movepick.cpp | 33 ++++++++++++--------- src/movepick.h | 12 +++----- src/search.cpp | 74 ++++++++++++++++++++++++++++-------------------- src/search.h | 4 +++ 4 files changed, 72 insertions(+), 51 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index c5238494..e248b729 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -67,22 +67,21 @@ namespace { /// search captures, promotions, and some checks) and how important good move /// ordering is at the current node. -MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats& h, - const CounterMoveStats& cmh, const CounterMoveStats& fmh, - Move cm, Search::Stack* s) - : pos(p), history(h), counterMoveHistory(&cmh), - followupMoveHistory(&fmh), ss(s), countermove(cm), depth(d) { +MovePicker::MovePicker(const Position& p, Move ttm, Depth d, Search::Stack* s) + : pos(p), ss(s), depth(d) { assert(d > DEPTH_ZERO); + Square prevSq = to_sq((ss-1)->currentMove); + countermove = pos.this_thread()->counterMoves[pos.piece_on(prevSq)][prevSq]; + stage = pos.checkers() ? EVASION : MAIN_SEARCH; ttMove = ttm && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE; endMoves += (ttMove != MOVE_NONE); } -MovePicker::MovePicker(const Position& p, Move ttm, Depth d, - const HistoryStats& h, Square s) - : pos(p), history(h) { +MovePicker::MovePicker(const Position& p, Move ttm, Depth d, Square s) + : pos(p) { assert(d <= DEPTH_ZERO); @@ -106,8 +105,8 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, endMoves += (ttMove != MOVE_NONE); } -MovePicker::MovePicker(const Position& p, Move ttm, const HistoryStats& h, Value th) - : pos(p), history(h), threshold(th) { +MovePicker::MovePicker(const Position& p, Move ttm, Value th) + : pos(p), threshold(th) { assert(!pos.checkers()); @@ -142,10 +141,17 @@ void MovePicker::score() { template<> void MovePicker::score() { + const HistoryStats& history = pos.this_thread()->history; + + const CounterMoveStats* cm = (ss-1)->counterMoves; + const CounterMoveStats* fm = (ss-2)->counterMoves; + const CounterMoveStats* f2 = (ss-4)->counterMoves; + for (auto& m : *this) - m.value = history[pos.moved_piece(m)][to_sq(m)] - + (*counterMoveHistory )[pos.moved_piece(m)][to_sq(m)] - + (*followupMoveHistory)[pos.moved_piece(m)][to_sq(m)]; + m.value = history[pos.moved_piece(m)][to_sq(m)] + + (cm ? (*cm)[pos.moved_piece(m)][to_sq(m)] : VALUE_ZERO) + + (fm ? (*fm)[pos.moved_piece(m)][to_sq(m)] : VALUE_ZERO) + + (f2 ? (*f2)[pos.moved_piece(m)][to_sq(m)] : VALUE_ZERO); } template<> @@ -153,6 +159,7 @@ void MovePicker::score() { // Try winning and equal captures ordered by MVV/LVA, then non-captures ordered // by history value, then bad captures and quiet moves with a negative SEE ordered // by SEE value. + const HistoryStats& history = pos.this_thread()->history; Value see; for (auto& m : *this) diff --git a/src/movepick.h b/src/movepick.h index 4c2beb96..fc090d29 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -79,10 +79,9 @@ public: MovePicker(const MovePicker&) = delete; MovePicker& operator=(const MovePicker&) = delete; - MovePicker(const Position&, Move, const HistoryStats&, Value); - MovePicker(const Position&, Move, Depth, const HistoryStats&, Square); - MovePicker(const Position&, Move, Depth, const HistoryStats&, - const CounterMoveStats&, const CounterMoveStats&, Move, Search::Stack*); + MovePicker(const Position&, Move, Value); + MovePicker(const Position&, Move, Depth, Square); + MovePicker(const Position&, Move, Depth, Search::Stack*); Move next_move(); @@ -93,10 +92,7 @@ private: ExtMove* end() { return endMoves; } const Position& pos; - const HistoryStats& history; - const CounterMoveStats* counterMoveHistory; - const CounterMoveStats* followupMoveHistory; - Search::Stack* ss; + const Search::Stack* ss; Move countermove; Depth depth; Move ttMove; diff --git a/src/search.cpp b/src/search.cpp index 31764533..847fc762 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -380,12 +380,12 @@ void MainThread::search() { void Thread::search() { - Stack stack[MAX_PLY+4], *ss = stack+2; // To allow referencing (ss-2) and (ss+2) + Stack stack[MAX_PLY+7], *ss = stack+5; // To allow referencing (ss-5) and (ss+2) Value bestValue, alpha, beta, delta; Move easyMove = MOVE_NONE; MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); - std::memset(ss-2, 0, 5 * sizeof(Stack)); + std::memset(ss-5, 0, 8 * sizeof(Stack)); bestValue = delta = alpha = -VALUE_INFINITE; beta = VALUE_INFINITE; @@ -657,6 +657,7 @@ namespace { assert(0 <= ss->ply && ss->ply < MAX_PLY); ss->currentMove = (ss+1)->excludedMove = bestMove = MOVE_NONE; + ss->counterMoves = nullptr; (ss+1)->skipEarlyPruning = false; (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; @@ -780,6 +781,7 @@ namespace { && pos.non_pawn_material(pos.side_to_move())) { ss->currentMove = MOVE_NULL; + ss->counterMoves = nullptr; assert(eval - beta >= 0); @@ -828,13 +830,14 @@ namespace { assert((ss-1)->currentMove != MOVE_NONE); assert((ss-1)->currentMove != MOVE_NULL); - MovePicker mp(pos, ttMove, thisThread->history, PieceValue[MG][pos.captured_piece_type()]); + MovePicker mp(pos, ttMove, PieceValue[MG][pos.captured_piece_type()]); CheckInfo ci(pos); while ((move = mp.next_move()) != MOVE_NONE) if (pos.legal(move, ci.pinned)) { ss->currentMove = move; + ss->counterMoves = &CounterMoveHistory[pos.moved_piece(move)][to_sq(move)]; pos.do_move(move, st, pos.gives_check(move, ci)); value = -search(pos, ss+1, -rbeta, -rbeta+1, rdepth, !cutNode); pos.undo_move(move); @@ -860,12 +863,9 @@ namespace { moves_loop: // When in check search starts from here Square prevSq = to_sq((ss-1)->currentMove); - Square ownPrevSq = to_sq((ss-2)->currentMove); - Move cm = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq]; const CounterMoveStats& cmh = CounterMoveHistory[pos.piece_on(prevSq)][prevSq]; - const CounterMoveStats& fmh = CounterMoveHistory[pos.piece_on(ownPrevSq)][ownPrevSq]; - MovePicker mp(pos, ttMove, depth, thisThread->history, cmh, fmh, cm, ss); + MovePicker mp(pos, ttMove, depth, ss); CheckInfo ci(pos); value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc improving = ss->staticEval >= (ss-2)->staticEval @@ -992,6 +992,7 @@ moves_loop: // When in check search starts from here } ss->currentMove = move; + ss->counterMoves = &CounterMoveHistory[pos.moved_piece(move)][to_sq(move)]; // Step 14. Make the move pos.do_move(move, st, givesCheck); @@ -1153,13 +1154,17 @@ moves_loop: // When in check search starts from here && !bestMove && !inCheck && !pos.captured_piece_type() - && is_ok((ss - 1)->currentMove) - && is_ok((ss - 2)->currentMove)) + && is_ok((ss-1)->currentMove)) { Value bonus = Value((depth / ONE_PLY) * (depth / ONE_PLY) + depth / ONE_PLY - 1); - Square prevPrevSq = to_sq((ss - 2)->currentMove); - CounterMoveStats& prevCmh = CounterMoveHistory[pos.piece_on(prevPrevSq)][prevPrevSq]; - prevCmh.update(pos.piece_on(prevSq), prevSq, bonus); + if ((ss-2)->counterMoves) + (ss-2)->counterMoves->update(pos.piece_on(prevSq), prevSq, bonus); + + if ((ss-3)->counterMoves) + (ss-3)->counterMoves->update(pos.piece_on(prevSq), prevSq, bonus); + + if ((ss-5)->counterMoves) + (ss-5)->counterMoves->update(pos.piece_on(prevSq), prevSq, bonus); } tte->save(posKey, value_to_tt(bestValue, ss->ply), @@ -1280,7 +1285,7 @@ moves_loop: // When in check search starts from here // to search the moves. Because the depth is <= 0 here, only captures, // queen promotions and checks (only if depth >= DEPTH_QS_CHECKS) will // be generated. - MovePicker mp(pos, ttMove, depth, pos.this_thread()->history, to_sq((ss-1)->currentMove)); + MovePicker mp(pos, ttMove, depth, to_sq((ss-1)->currentMove)); CheckInfo ci(pos); // Loop through the moves until no moves remain or a beta cutoff occurs @@ -1434,42 +1439,51 @@ moves_loop: // When in check search starts from here Value bonus = Value((depth / ONE_PLY) * (depth / ONE_PLY) + depth / ONE_PLY - 1); Square prevSq = to_sq((ss-1)->currentMove); - Square ownPrevSq = to_sq((ss-2)->currentMove); - CounterMoveStats& cmh = CounterMoveHistory[pos.piece_on(prevSq)][prevSq]; - CounterMoveStats& fmh = CounterMoveHistory[pos.piece_on(ownPrevSq)][ownPrevSq]; + CounterMoveStats* cmh = (ss-1)->counterMoves; + CounterMoveStats* fmh = (ss-2)->counterMoves; + CounterMoveStats* fmh2 = (ss-4)->counterMoves; Thread* thisThread = pos.this_thread(); thisThread->history.update(pos.moved_piece(move), to_sq(move), bonus); - if (is_ok((ss-1)->currentMove)) + if (cmh) { thisThread->counterMoves.update(pos.piece_on(prevSq), prevSq, move); - cmh.update(pos.moved_piece(move), to_sq(move), bonus); + cmh->update(pos.moved_piece(move), to_sq(move), bonus); } - if (is_ok((ss-2)->currentMove)) - fmh.update(pos.moved_piece(move), to_sq(move), bonus); + if (fmh) + fmh->update(pos.moved_piece(move), to_sq(move), bonus); + + if (fmh2) + fmh2->update(pos.moved_piece(move), to_sq(move), bonus); // Decrease all the other played quiet moves for (int i = 0; i < quietsCnt; ++i) { thisThread->history.update(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus); - if (is_ok((ss-1)->currentMove)) - cmh.update(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus); + if (cmh) + cmh->update(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus); - if (is_ok((ss-2)->currentMove)) - fmh.update(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus); + if (fmh) + fmh->update(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus); + + if (fmh2) + fmh2->update(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus); } // Extra penalty for a quiet TT move in previous ply when it gets refuted - if ( (ss-1)->moveCount == 1 - && !pos.captured_piece_type() - && is_ok((ss-2)->currentMove)) + if ((ss-1)->moveCount == 1 && !pos.captured_piece_type()) { - Square prevPrevSq = to_sq((ss-2)->currentMove); - CounterMoveStats& prevCmh = CounterMoveHistory[pos.piece_on(prevPrevSq)][prevPrevSq]; - prevCmh.update(pos.piece_on(prevSq), prevSq, -bonus - 2 * (depth + 1) / ONE_PLY); + if ((ss-2)->counterMoves) + (ss-2)->counterMoves->update(pos.piece_on(prevSq), prevSq, -bonus - 2 * (depth + 1) / ONE_PLY); + + if ((ss-3)->counterMoves) + (ss-3)->counterMoves->update(pos.piece_on(prevSq), prevSq, -bonus - 2 * (depth + 1) / ONE_PLY); + + if ((ss-5)->counterMoves) + (ss-5)->counterMoves->update(pos.piece_on(prevSq), prevSq, -bonus - 2 * (depth + 1) / ONE_PLY); } } diff --git a/src/search.h b/src/search.h index d722af1e..73d8cdfa 100644 --- a/src/search.h +++ b/src/search.h @@ -28,6 +28,9 @@ #include "position.h" #include "types.h" +template struct Stats; +typedef Stats CounterMoveStats; + namespace Search { /// Stack struct keeps track of the information we need to remember from nodes @@ -43,6 +46,7 @@ struct Stack { Value staticEval; bool skipEarlyPruning; int moveCount; + CounterMoveStats* counterMoves; }; /// RootMove struct is used for moves at the root of the tree. For each root move From 94e41274bba2d8a2f2d58aaa711df5872309d66c Mon Sep 17 00:00:00 2001 From: Marco Costalba Date: Sun, 17 Apr 2016 21:31:19 +0200 Subject: [PATCH 3/6] Fix incorrect draw detection In this position we should have draw for repetition: position fen rnbqkbnr/2pppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 moves g1f3 g8f6 f3g1 go infinite But latest patch broke it. Actually we had two(!) very subtle bugs, the first is that Position::set() clears the passed state and in particular 'previous' member, so that on passing setupStates, 'previous' pointer was reset. Second bug is even more subtle: SetupStates was based on std::vector as container, but when vector grows, std::vector copies all its contents to a new location invalidating all references to its entries. Because all StateInfo records are linked by 'previous' pointer, this made pointers go stale upon adding more element to setupStates. So revert to use a std::deque that ensures references are preserved when pushing back new elements. No functional change. --- src/benchmark.cpp | 2 +- src/position.h | 4 +++- src/thread.cpp | 4 ++++ src/uci.cpp | 4 ++-- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 2978f346..95125404 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -148,7 +148,7 @@ void benchmark(const Position& current, istream& is) { for (size_t i = 0; i < fens.size(); ++i) { - StateListPtr states(new std::vector(1)); + StateListPtr states(new std::deque(1)); pos.set(fens[i], Options["UCI_Chess960"], &states->back(), Threads.main()); cerr << "\nPosition: " << i + 1 << '/' << fens.size() << endl; diff --git a/src/position.h b/src/position.h index d44ed009..c3ba5ac8 100644 --- a/src/position.h +++ b/src/position.h @@ -23,6 +23,7 @@ #include #include // For offsetof() +#include #include // For std::unique_ptr #include #include @@ -77,7 +78,8 @@ struct StateInfo { StateInfo* previous; }; -typedef std::unique_ptr> StateListPtr; +// In a std::deque references to elements are unaffected upon resizing +typedef std::unique_ptr> StateListPtr; /// Position class stores information regarding the board representation as diff --git a/src/thread.cpp b/src/thread.cpp index dc4ec05e..4dc7d9e9 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -190,6 +190,8 @@ void ThreadPool::start_thinking(const Position& pos, StateListPtr& states, if (states.get()) setupStates = std::move(states); // Ownership transfer, states is now empty + StateInfo tmp = setupStates->back(); + for (Thread* th : Threads) { th->maxPly = 0; @@ -198,5 +200,7 @@ void ThreadPool::start_thinking(const Position& pos, StateListPtr& states, th->rootPos.set(pos.fen(), pos.is_chess960(), &setupStates->back(), th); } + setupStates->back() = tmp; // Restore st->previous, cleared by Position::set() + main()->start_searching(); } diff --git a/src/uci.cpp b/src/uci.cpp index cfd253b1..a9811a9a 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -42,7 +42,7 @@ namespace { // A list to keep track of the position states along the setup moves (from the // start position to the position just before the search starts). Needed by // 'draw by repetition' detection. - StateListPtr States(new std::vector(1)); + StateListPtr States(new std::deque(1)); // position() is called when engine receives the "position" UCI command. @@ -68,7 +68,7 @@ namespace { else return; - States = StateListPtr(new std::vector(1)); + States = StateListPtr(new std::deque(1)); pos.set(fen, Options["UCI_Chess960"], &States->back(), Threads.main()); // Parse move list (if any) From c73706243672bf36b0fef58e817f843cb341d8ca Mon Sep 17 00:00:00 2001 From: DU-jdto Date: Thu, 21 Apr 2016 14:23:40 +1000 Subject: [PATCH 4/6] Remove some pointless micro-optimizations Seems to give around 1% speed-up for CPUs with popcnt support. Seems to give a very minor speed-up for CPUs without popcnt. No functional change Resolves #646 --- src/evaluate.cpp | 22 ++++++---------------- src/movegen.cpp | 2 +- src/position.cpp | 6 ++---- 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 9739a001..0f2744d9 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -234,7 +234,7 @@ namespace { { ei.kingRing[Them] = b | shift_bb(b); b &= ei.attackedBy[Us][PAWN]; - ei.kingAttackersCount[Us] = b ? popcount(b) : 0; + ei.kingAttackersCount[Us] = popcount(b); ei.kingAdjacentZoneAttacksCount[Us] = ei.kingAttackersWeight[Us] = 0; } else @@ -276,9 +276,7 @@ namespace { { ei.kingAttackersCount[Us]++; ei.kingAttackersWeight[Us] += KingAttackWeights[Pt]; - bb = b & ei.attackedBy[Them][KING]; - if (bb) - ei.kingAdjacentZoneAttacksCount[Us] += popcount(bb); + ei.kingAdjacentZoneAttacksCount[Us] += popcount(b & ei.attackedBy[Them][KING]); } if (Pt == QUEEN) @@ -331,11 +329,7 @@ namespace { { // Bonus for aligning with enemy pawns on the same rank/file if (relative_rank(Us, s) >= RANK_5) - { - Bitboard alignedPawns = pos.pieces(Them, PAWN) & PseudoAttacks[ROOK][s]; - if (alignedPawns) - score += RookOnPawn * popcount(alignedPawns); - } + score += RookOnPawn * popcount(pos.pieces(Them, PAWN) & PseudoAttacks[ROOK][s]); // Bonus when on an open or semi-open file if (ei.pi->semiopen_file(Us, file_of(s))) @@ -417,8 +411,7 @@ namespace { | ei.attackedBy[Them][BISHOP] | ei.attackedBy[Them][ROOK] | ei.attackedBy[Them][KING]; - if (b) - attackUnits += QueenContactCheck * popcount(b); + attackUnits += QueenContactCheck * popcount(b); } // Analyse the enemy's safe distance checks for sliders and knights @@ -514,9 +507,7 @@ namespace { while (b) score += Threat[Rook ][type_of(pos.piece_on(pop_lsb(&b)))]; - b = weak & ~ei.attackedBy[Them][ALL_PIECES]; - if (b) - score += Hanging * popcount(b); + score += Hanging * popcount(weak & ~ei.attackedBy[Them][ALL_PIECES]); b = weak & ei.attackedBy[Us][KING]; if (b) @@ -535,8 +526,7 @@ namespace { & pos.pieces(Them) & ~ei.attackedBy[Us][PAWN]; - if (b) - score += ThreatByPawnPush * popcount(b); + score += ThreatByPawnPush * popcount(b); if (DoTrace) Trace::add(THREAT, Us, score); diff --git a/src/movegen.cpp b/src/movegen.cpp index 6ef4be4f..3622f18e 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -239,7 +239,7 @@ namespace { && !(PseudoAttacks[Pt][from] & target & ci->checkSquares[Pt])) continue; - if (ci->dcCandidates && (ci->dcCandidates & from)) + if (ci->dcCandidates & from) continue; } diff --git a/src/position.cpp b/src/position.cpp index 6e8b03d1..cd91b07f 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -501,8 +501,7 @@ bool Position::legal(Move m, Bitboard pinned) const { // A non-king move is legal if and only if it is not pinned or it // is moving along the ray towards or away from the king. - return !pinned - || !(pinned & from) + return !(pinned & from) || aligned(from, to_sq(m), square(us)); } @@ -595,8 +594,7 @@ bool Position::gives_check(Move m, const CheckInfo& ci) const { return true; // Is there a discovered check? - if ( ci.dcCandidates - && (ci.dcCandidates & from) + if ( (ci.dcCandidates & from) && !aligned(from, to, ci.ksq)) return true; From 4048bae47bf29f431b1c6c9629675ff6cffde28f Mon Sep 17 00:00:00 2001 From: erbsenzaehler Date: Sun, 24 Apr 2016 00:55:56 +0100 Subject: [PATCH 5/6] Use -O3 for all compilers (including ICC) There seems to be no benefit from using -fast over -O3 with icc. So use -O3 everywhere. No functional change Resolves #652 --- src/Makefile | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/Makefile b/src/Makefile index 294634f6..a7d749a4 100644 --- a/src/Makefile +++ b/src/Makefile @@ -231,18 +231,19 @@ ifneq ($(comp),mingw) endif endif -### 3.4 Debugging +### 3.2 Debugging ifeq ($(debug),no) CXXFLAGS += -DNDEBUG else CXXFLAGS += -g endif -### 3.5 Optimization +### 3.3 Optimization ifeq ($(optimize),yes) + CXXFLAGS += -O3 + ifeq ($(comp),gcc) - CXXFLAGS += -O3 ifeq ($(UNAME),Darwin) ifeq ($(arch),i386) @@ -258,21 +259,13 @@ ifeq ($(optimize),yes) endif endif - ifeq ($(comp),mingw) - CXXFLAGS += -O3 - endif - ifeq ($(comp),icc) ifeq ($(UNAME),Darwin) - CXXFLAGS += -fast -mdynamic-no-pic - else - CXXFLAGS += -fast + CXXFLAGS += -mdynamic-no-pic endif endif ifeq ($(comp),clang) - CXXFLAGS += -O3 - ifeq ($(UNAME),Darwin) ifeq ($(pext),no) CXXFLAGS += -flto @@ -288,12 +281,12 @@ ifeq ($(optimize),yes) endif endif -### 3.6. Bits +### 3.4 Bits ifeq ($(bits),64) CXXFLAGS += -DIS_64BIT endif -### 3.7 prefetch +### 3.5 prefetch ifeq ($(prefetch),yes) ifeq ($(sse),yes) CXXFLAGS += -msse @@ -303,7 +296,7 @@ else CXXFLAGS += -DNO_PREFETCH endif -### 3.9 popcnt +### 3.6 popcnt ifeq ($(popcnt),yes) ifeq ($(comp),icc) CXXFLAGS += -msse3 -DUSE_POPCNT @@ -312,7 +305,7 @@ ifeq ($(popcnt),yes) endif endif -### 3.10 pext +### 3.7 pext ifeq ($(pext),yes) CXXFLAGS += -DUSE_PEXT ifeq ($(comp),$(filter $(comp),gcc clang mingw)) @@ -320,7 +313,7 @@ ifeq ($(pext),yes) endif endif -### 3.11 Link Time Optimization, it works since gcc 4.5 but not on mingw under Windows. +### 3.8 Link Time Optimization, it works since gcc 4.5 but not on mingw under Windows. ### This is a mix of compile and link time options because the lto link phase ### needs access to the optimization flags. ifeq ($(comp),gcc) @@ -343,7 +336,7 @@ ifeq ($(comp),mingw) endif endif -### 3.12 Android 5 can only run position independent executables. Note that this +### 3.9 Android 5 can only run position independent executables. Note that this ### breaks Android 4.0 and earlier. ifeq ($(arch),armv7) CXXFLAGS += -fPIE From e082112cfeb6a40ca592a15983cdedb0210daf3a Mon Sep 17 00:00:00 2001 From: VoyagerOne Date: Sun, 17 Apr 2016 20:04:34 -0400 Subject: [PATCH 6/6] Use FMHs to assist with LMR formula. STC: LLR: 2.99 (-2.94,2.94) [0.00,5.00] Total: 52232 W: 9654 L: 9304 D: 33274 LTC: LLR: 2.97 (-2.94,2.94) [0.00,5.00] Total: 115988 W: 15550 L: 15049 D: 85389 Bench: 7890808 Resolves #651 --- src/search.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 847fc762..a50f7990 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1006,6 +1006,11 @@ moves_loop: // When in check search starts from here Depth r = reduction(improving, depth, moveCount); Value hValue = thisThread->history[pos.piece_on(to_sq(move))][to_sq(move)]; Value cmhValue = cmh[pos.piece_on(to_sq(move))][to_sq(move)]; + + const CounterMoveStats* fm = (ss - 2)->counterMoves; + const CounterMoveStats* fm2 = (ss - 4)->counterMoves; + Value fmValue = (fm ? (*fm)[pos.piece_on(to_sq(move))][to_sq(move)] : VALUE_ZERO); + Value fm2Value = (fm2 ? (*fm2)[pos.piece_on(to_sq(move))][to_sq(move)] : VALUE_ZERO); // Increase reduction for cut nodes and moves with a bad history if ( (!PvNode && cutNode) @@ -1013,7 +1018,7 @@ moves_loop: // When in check search starts from here r += ONE_PLY; // Decrease/increase reduction for moves with a good/bad history - int rHist = (hValue + cmhValue) / 14980; + int rHist = (hValue + cmhValue + fmValue + fm2Value) / 20000; r = std::max(DEPTH_ZERO, r - rHist * ONE_PLY); // Decrease reduction for moves that escape a capture. Filter out