From b254eef594f0af0b1d712b0341ee4ef781493ada Mon Sep 17 00:00:00 2001 From: Shawn Xu <xu107288696@gmail.com> Date: Thu, 24 Oct 2024 13:16:49 -0700 Subject: [PATCH] refactor history code to history.h bench 1281912 --- src/Makefile | 2 +- src/history.h | 185 +++++++++++++++++++++++++++++++++++++++++++++++ src/movepick.cpp | 2 + src/movepick.h | 163 +---------------------------------------- src/search.cpp | 3 +- src/search.h | 2 +- 6 files changed, 193 insertions(+), 164 deletions(-) create mode 100644 src/history.h diff --git a/src/Makefile b/src/Makefile index 4307b7c7473..e7f8ce556bb 100644 --- a/src/Makefile +++ b/src/Makefile @@ -57,7 +57,7 @@ SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp engine.cpp score.cpp memory.cpp -HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ +HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history.h \ nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ nnue/layers/affine_transform_sparse_input.h nnue/layers/clipped_relu.h nnue/layers/simd.h \ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ diff --git a/src/history.h b/src/history.h new file mode 100644 index 00000000000..8d14a7a7cb1 --- /dev/null +++ b/src/history.h @@ -0,0 +1,185 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef HISTORY_H_INCLUDED +#define HISTORY_H_INCLUDED + +#include <algorithm> +#include <array> +#include <cassert> +#include <cmath> +#include <cstdint> +#include <cstdlib> +#include <limits> +#include <type_traits> // IWYU pragma: keep + +#include "position.h" + +namespace Stockfish { + +constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 +constexpr int CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 +constexpr int CORRECTION_HISTORY_LIMIT = 1024; +constexpr int LOW_PLY_HISTORY_SIZE = 4; + +static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, + "PAWN_HISTORY_SIZE has to be a power of 2"); + +static_assert((CORRECTION_HISTORY_SIZE & (CORRECTION_HISTORY_SIZE - 1)) == 0, + "CORRECTION_HISTORY_SIZE has to be a power of 2"); + +enum PawnHistoryType { + Normal, + Correction +}; + +template<PawnHistoryType T = Normal> +inline int pawn_structure_index(const Position& pos) { + return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : CORRECTION_HISTORY_SIZE) - 1); +} + +inline int major_piece_index(const Position& pos) { + return pos.major_piece_key() & (CORRECTION_HISTORY_SIZE - 1); +} + +inline int minor_piece_index(const Position& pos) { + return pos.minor_piece_key() & (CORRECTION_HISTORY_SIZE - 1); +} + +template<Color c> +inline int non_pawn_index(const Position& pos) { + return pos.non_pawn_key(c) & (CORRECTION_HISTORY_SIZE - 1); +} + +// StatsEntry stores the stat table value. It is usually a number but could +// be a move or even a nested history. We use a class instead of a naked value +// to directly call history update operator<<() on the entry so to use stats +// tables at caller sites as simple multi-dim arrays. +template<typename T, int D> +class StatsEntry { + + T entry; + + public: + void operator=(const T& v) { entry = v; } + T* operator&() { return &entry; } + T* operator->() { return &entry; } + operator const T&() const { return entry; } + + void operator<<(int bonus) { + static_assert(D <= std::numeric_limits<T>::max(), "D overflows T"); + + // Make sure that bonus is in range [-D, D] + int clampedBonus = std::clamp(bonus, -D, D); + entry += clampedBonus - entry * std::abs(clampedBonus) / D; + + assert(std::abs(entry) <= D); + } +}; + +// Stats is a generic N-dimensional array used to store various statistics. +// The first template parameter T is the base type of the array, and the second +// template parameter D limits the range of updates in [-D, D] when we update +// values with the << operator, while the last parameters (Size and Sizes) +// encode the dimensions of the array. +template<typename T, int D, int Size, int... Sizes> +struct Stats: public std::array<Stats<T, D, Sizes...>, Size> { + using stats = Stats<T, D, Size, Sizes...>; + + void fill(const T& v) { + + // For standard-layout 'this' points to the first struct member + assert(std::is_standard_layout_v<stats>); + + using entry = StatsEntry<T, D>; + entry* p = reinterpret_cast<entry*>(this); + std::fill(p, p + sizeof(*this) / sizeof(entry), v); + } +}; + +template<typename T, int D, int Size> +struct Stats<T, D, Size>: public std::array<StatsEntry<T, D>, Size> {}; + +// In stats table, D=0 means that the template parameter is not used +enum StatsParams { + NOT_USED = 0 +}; +enum StatsType { + NoCaptures, + Captures +}; + +// ButterflyHistory records how often quiet moves have been successful or unsuccessful +// during the current search, and is used for reduction and move ordering decisions. +// It uses 2 tables (one for each color) indexed by the move's from and to squares, +// see https://www.chessprogramming.org/Butterfly_Boards (~11 elo) +using ButterflyHistory = Stats<int16_t, 7183, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)>; + +// LowPlyHistory is adressed by play and move's from and to squares, used +// to improve move ordering near the root +using LowPlyHistory = Stats<int16_t, 7183, LOW_PLY_HISTORY_SIZE, int(SQUARE_NB) * int(SQUARE_NB)>; + +// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] +using CapturePieceToHistory = Stats<int16_t, 10692, PIECE_NB, SQUARE_NB, PIECE_TYPE_NB>; + +// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to] +using PieceToHistory = Stats<int16_t, 29952, PIECE_NB, SQUARE_NB>; + +// ContinuationHistory is the combined history of a given pair of moves, usually +// the current one given a previous one. The nested history table is based on +// PieceToHistory instead of ButterflyBoards. +// (~63 elo) +using ContinuationHistory = Stats<PieceToHistory, NOT_USED, PIECE_NB, SQUARE_NB>; + +// PawnHistory is addressed by the pawn structure and a move's [piece][to] +using PawnHistory = Stats<int16_t, 8192, PAWN_HISTORY_SIZE, PIECE_NB, SQUARE_NB>; + +// Correction histories record differences between the static evaluation of +// positions and their search score. It is used to improve the static evaluation +// used by some search heuristics. +// see https://www.chessprogramming.org/Static_Evaluation_Correction_History +enum CorrHistType { + Pawn, // By color and pawn structure + Major, // By color and positions of major pieces (Queen, Rook) and King + Minor, // By color and positions of minor pieces (Knight, Bishop) and King + NonPawn, // By color and non-pawn material positions + PieceTo, // By [piece][to] move + Continuation, // Combined history of move pairs +}; + +template<CorrHistType _> +struct CorrHistTypedef { + using type = Stats<int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB, CORRECTION_HISTORY_SIZE>; +}; + +template<> +struct CorrHistTypedef<PieceTo> { + using type = Stats<int16_t, CORRECTION_HISTORY_LIMIT, PIECE_NB, SQUARE_NB>; +}; + +template<> +struct CorrHistTypedef<Continuation> { + using type = Stats<CorrHistTypedef<PieceTo>::type, NOT_USED, PIECE_NB, SQUARE_NB>; +}; + +template<CorrHistType T> +using CorrectionHistory = typename CorrHistTypedef<T>::type; + +} // namespace Stockfish + +#endif // #ifndef HISTORY_H_INCLUDED diff --git a/src/movepick.cpp b/src/movepick.cpp index 2a1fb837348..720f2e031ea 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -19,7 +19,9 @@ #include "movepick.h" #include <algorithm> +#include <array> #include <cassert> +#include <limits> #include <utility> #include "bitboard.h" diff --git a/src/movepick.h b/src/movepick.h index f8f84d02474..0278b70ec82 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -19,172 +19,13 @@ #ifndef MOVEPICK_H_INCLUDED #define MOVEPICK_H_INCLUDED -#include <algorithm> -#include <array> -#include <cassert> -#include <cmath> -#include <cstdint> -#include <cstdlib> -#include <limits> -#include <type_traits> // IWYU pragma: keep - +#include "history.h" #include "movegen.h" -#include "position.h" #include "types.h" namespace Stockfish { -constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 -constexpr int CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 -constexpr int CORRECTION_HISTORY_LIMIT = 1024; -constexpr int LOW_PLY_HISTORY_SIZE = 4; - -static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, - "PAWN_HISTORY_SIZE has to be a power of 2"); - -static_assert((CORRECTION_HISTORY_SIZE & (CORRECTION_HISTORY_SIZE - 1)) == 0, - "CORRECTION_HISTORY_SIZE has to be a power of 2"); - -enum PawnHistoryType { - Normal, - Correction -}; - -template<PawnHistoryType T = Normal> -inline int pawn_structure_index(const Position& pos) { - return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : CORRECTION_HISTORY_SIZE) - 1); -} - -inline int material_index(const Position& pos) { - return pos.material_key() & (CORRECTION_HISTORY_SIZE - 1); -} - -inline int major_piece_index(const Position& pos) { - return pos.major_piece_key() & (CORRECTION_HISTORY_SIZE - 1); -} - -inline int minor_piece_index(const Position& pos) { - return pos.minor_piece_key() & (CORRECTION_HISTORY_SIZE - 1); -} - -template<Color c> -inline int non_pawn_index(const Position& pos) { - return pos.non_pawn_key(c) & (CORRECTION_HISTORY_SIZE - 1); -} - -// StatsEntry stores the stat table value. It is usually a number but could -// be a move or even a nested history. We use a class instead of a naked value -// to directly call history update operator<<() on the entry so to use stats -// tables at caller sites as simple multi-dim arrays. -template<typename T, int D> -class StatsEntry { - - T entry; - - public: - void operator=(const T& v) { entry = v; } - T* operator&() { return &entry; } - T* operator->() { return &entry; } - operator const T&() const { return entry; } - - void operator<<(int bonus) { - static_assert(D <= std::numeric_limits<T>::max(), "D overflows T"); - - // Make sure that bonus is in range [-D, D] - int clampedBonus = std::clamp(bonus, -D, D); - entry += clampedBonus - entry * std::abs(clampedBonus) / D; - - assert(std::abs(entry) <= D); - } -}; - -// Stats is a generic N-dimensional array used to store various statistics. -// The first template parameter T is the base type of the array, and the second -// template parameter D limits the range of updates in [-D, D] when we update -// values with the << operator, while the last parameters (Size and Sizes) -// encode the dimensions of the array. -template<typename T, int D, int Size, int... Sizes> -struct Stats: public std::array<Stats<T, D, Sizes...>, Size> { - using stats = Stats<T, D, Size, Sizes...>; - - void fill(const T& v) { - - // For standard-layout 'this' points to the first struct member - assert(std::is_standard_layout_v<stats>); - - using entry = StatsEntry<T, D>; - entry* p = reinterpret_cast<entry*>(this); - std::fill(p, p + sizeof(*this) / sizeof(entry), v); - } -}; - -template<typename T, int D, int Size> -struct Stats<T, D, Size>: public std::array<StatsEntry<T, D>, Size> {}; - -// In stats table, D=0 means that the template parameter is not used -enum StatsParams { - NOT_USED = 0 -}; -enum StatsType { - NoCaptures, - Captures -}; - -// ButterflyHistory records how often quiet moves have been successful or unsuccessful -// during the current search, and is used for reduction and move ordering decisions. -// It uses 2 tables (one for each color) indexed by the move's from and to squares, -// see https://www.chessprogramming.org/Butterfly_Boards (~11 elo) -using ButterflyHistory = Stats<int16_t, 7183, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)>; - -// LowPlyHistory is adressed by play and move's from and to squares, used -// to improve move ordering near the root -using LowPlyHistory = Stats<int16_t, 7183, LOW_PLY_HISTORY_SIZE, int(SQUARE_NB) * int(SQUARE_NB)>; - -// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] -using CapturePieceToHistory = Stats<int16_t, 10692, PIECE_NB, SQUARE_NB, PIECE_TYPE_NB>; - -// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to] -using PieceToHistory = Stats<int16_t, 29952, PIECE_NB, SQUARE_NB>; - -// ContinuationHistory is the combined history of a given pair of moves, usually -// the current one given a previous one. The nested history table is based on -// PieceToHistory instead of ButterflyBoards. -// (~63 elo) -using ContinuationHistory = Stats<PieceToHistory, NOT_USED, PIECE_NB, SQUARE_NB>; - -// PawnHistory is addressed by the pawn structure and a move's [piece][to] -using PawnHistory = Stats<int16_t, 8192, PAWN_HISTORY_SIZE, PIECE_NB, SQUARE_NB>; - -// Correction histories record differences between the static evaluation of -// positions and their search score. It is used to improve the static evaluation -// used by some search heuristics. -// see https://www.chessprogramming.org/Static_Evaluation_Correction_History -enum CorrHistType { - Pawn, // By color and pawn structure - Major, // By color and positions of major pieces (Queen, Rook) and King - Minor, // By color and positions of minor pieces (Knight, Bishop) and King - NonPawn, // By color and non-pawn material positions - PieceTo, // By [piece][to] move - Continuation, // Combined history of move pairs -}; - -template<CorrHistType _> -struct CorrHistTypedef { - using type = Stats<int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB, CORRECTION_HISTORY_SIZE>; -}; - -template<> -struct CorrHistTypedef<PieceTo> { - using type = Stats<int16_t, CORRECTION_HISTORY_LIMIT, PIECE_NB, SQUARE_NB>; -}; - -template<> -struct CorrHistTypedef<Continuation> { - using type = Stats<CorrHistTypedef<PieceTo>::type, NOT_USED, PIECE_NB, SQUARE_NB>; -}; - -template<CorrHistType T> -using CorrectionHistory = typename CorrHistTypedef<T>::type; +class Position; // The MovePicker class is used to pick one pseudo-legal move at a time from the // current position. The most important method is next_move(), which emits one diff --git a/src/search.cpp b/src/search.cpp index 4864057c1b1..d3dea27baa7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -34,6 +34,7 @@ #include <utility> #include "evaluate.h" +#include "history.h" #include "misc.h" #include "movegen.h" #include "movepick.h" @@ -537,7 +538,7 @@ Value Search::Worker::search( // Dive into quiescence search when the depth reaches zero if (depth <= 0) - return qsearch < PvNode ? PV : NonPV > (pos, ss, alpha, beta); + return qsearch<PvNode ? PV : NonPV>(pos, ss, alpha, beta); // Limit the depth if extensions made it too large depth = std::min(depth, MAX_PLY - 1); diff --git a/src/search.h b/src/search.h index 751a398483f..b618855b9fc 100644 --- a/src/search.h +++ b/src/search.h @@ -31,8 +31,8 @@ #include <string_view> #include <vector> +#include "history.h" #include "misc.h" -#include "movepick.h" #include "nnue/network.h" #include "nnue/nnue_accumulator.h" #include "numa.h"