mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-22 12:47:08 +00:00
Shared pawn history
[Passed STC SMP](https://tests.stockfishchess.org/tests/view/694e506c572093c1986d7276): ``` LLR: 2.97 (-2.94,2.94) <0.00,2.00> Total: 14992 W: 3924 L: 3653 D: 7415 Ptnml(0-2): 20, 1547, 4090, 1820, 19 ``` [Passed LTC SMP](https://tests.stockfishchess.org/tests/live_elo/694ead61572093c1986d7365): ``` LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 41146 W: 10654 L: 10342 D: 20150 Ptnml(0-2): 17, 3999, 12225, 4319, 13 ``` [Passed a sanity check STC SMP post-refactoring](https://tests.stockfishchess.org/tests/view/69503997572093c1986d763a): ``` LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 46728 W: 12178 L: 11863 D: 22687 Ptnml(0-2): 82, 5093, 12685, 5436, 68 ``` (The large gain of the first STC was probably a fluke, and this result is more reasonable!) After shared correction history, Viz suggested we try sharing other histories, especially `pawnHistory`. As far as we're aware, sharing history besides correction history (like Caissa does) is novel. The implementation follows the same pattern as shared correction history – the size of the history table is scaled with `next_power_of_two(threadsInNumaNode)` and the entry is prefetched in `do_move`. A bit of refactoring was done to accommodate this new history. Note that we prefetch `&history->pawn_entry(*this)[pc][to]` rather than `&history->pawn_entry(*this)` because unlike the other entries, each entry contains multiple cache lines. closes https://github.com/official-stockfish/Stockfish/pull/6498 Bench: 2503391 Co-authored-by: Michael Chaly <Vizvezdenec@gmail.com>
This commit is contained in:
committed by
Disservin
co-authored by
Michael Chaly
parent
1780c1fd6e
commit
969285fa5d
+34
-14
@@ -35,22 +35,18 @@
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
constexpr int PAWN_HISTORY_SIZE = 8192; // has to be a power of 2
|
||||
constexpr int PAWN_HISTORY_BASE_SIZE = 8192; // has to be a power of 2
|
||||
constexpr int UINT_16_HISTORY_SIZE = std::numeric_limits<uint16_t>::max() + 1;
|
||||
constexpr int CORRHIST_BASE_SIZE = UINT_16_HISTORY_SIZE;
|
||||
constexpr int CORRECTION_HISTORY_LIMIT = 1024;
|
||||
constexpr int LOW_PLY_HISTORY_SIZE = 5;
|
||||
|
||||
static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0,
|
||||
"PAWN_HISTORY_SIZE has to be a power of 2");
|
||||
static_assert((PAWN_HISTORY_BASE_SIZE & (PAWN_HISTORY_BASE_SIZE - 1)) == 0,
|
||||
"PAWN_HISTORY_BASE_SIZE has to be a power of 2");
|
||||
|
||||
static_assert((CORRHIST_BASE_SIZE & (CORRHIST_BASE_SIZE - 1)) == 0,
|
||||
"CORRHIST_BASE_SIZE has to be a power of 2");
|
||||
|
||||
inline int pawn_history_index(const Position& pos) {
|
||||
return pos.pawn_key() & (PAWN_HISTORY_SIZE - 1);
|
||||
}
|
||||
|
||||
// StatsEntry is the container of various numerical statistics. We use a class
|
||||
// instead of a naked value to directly call history update operator<<() on
|
||||
// the entry. The first template parameter T is the base type of the array,
|
||||
@@ -96,6 +92,9 @@ enum StatsType {
|
||||
template<typename T, int D, std::size_t... Sizes>
|
||||
using Stats = MultiArray<StatsEntry<T, D>, Sizes...>;
|
||||
|
||||
template<typename T, int D, std::size_t... Sizes>
|
||||
using AtomicStats = MultiArray<StatsEntry<T, D, true>, Sizes...>;
|
||||
|
||||
// DynStats is a dynamically sized array of Stats, used for thread-shared histories
|
||||
// which should scale with the total number of threads. The SizeMultiplier gives
|
||||
// the per-thread allocation count of T.
|
||||
@@ -106,11 +105,13 @@ struct DynStats {
|
||||
data = make_unique_large_page<T[]>(size);
|
||||
}
|
||||
// Sets all values in the range to 0
|
||||
void clear_range(size_t start, size_t end) {
|
||||
void clear_range(int value, size_t threadIdx) {
|
||||
size_t start = threadIdx * SizeMultiplier;
|
||||
assert(start < size);
|
||||
assert(end <= size);
|
||||
T* fill_start = &(*this)[start];
|
||||
memset(reinterpret_cast<char*>(fill_start), 0, sizeof(T) * (end - start));
|
||||
size_t end = std::min(start + SizeMultiplier, size);
|
||||
|
||||
while (start < end)
|
||||
data[start++].fill(value);
|
||||
}
|
||||
size_t get_size() const { return size; }
|
||||
T& operator[](size_t index) {
|
||||
@@ -149,7 +150,8 @@ using PieceToHistory = Stats<std::int16_t, 30000, PIECE_NB, SQUARE_NB>;
|
||||
using ContinuationHistory = MultiArray<PieceToHistory, PIECE_NB, SQUARE_NB>;
|
||||
|
||||
// PawnHistory is addressed by the pawn structure and a move's [piece][to]
|
||||
using PawnHistory = Stats<std::int16_t, 8192, PAWN_HISTORY_SIZE, PIECE_NB, SQUARE_NB>;
|
||||
using PawnHistory =
|
||||
DynStats<AtomicStats<std::int16_t, 8192, PIECE_NB, SQUARE_NB>, PAWN_HISTORY_BASE_SIZE>;
|
||||
|
||||
// Correction histories record differences between the static evaluation of
|
||||
// positions and their search score. It is used to improve the static evaluation
|
||||
@@ -169,6 +171,13 @@ struct CorrectionBundle {
|
||||
StatsEntry<T, D, true> minor;
|
||||
StatsEntry<T, D, true> nonPawnWhite;
|
||||
StatsEntry<T, D, true> nonPawnBlack;
|
||||
|
||||
void operator=(T val) {
|
||||
pawn = val;
|
||||
minor = val;
|
||||
nonPawnWhite = val;
|
||||
nonPawnBlack = val;
|
||||
}
|
||||
};
|
||||
|
||||
namespace Detail {
|
||||
@@ -212,13 +221,22 @@ using TTMoveHistory = StatsEntry<std::int16_t, 8192>;
|
||||
// the indexing more efficient.
|
||||
struct SharedHistories {
|
||||
SharedHistories(size_t threadCount) :
|
||||
correctionHistory(threadCount) {
|
||||
correctionHistory(threadCount),
|
||||
pawnHistory(threadCount) {
|
||||
assert((threadCount & (threadCount - 1)) == 0 && threadCount != 0);
|
||||
sizeMinus1 = correctionHistory.get_size() - 1;
|
||||
pawnHistSizeMinus1 = pawnHistory.get_size() - 1;
|
||||
}
|
||||
|
||||
size_t get_size() const { return sizeMinus1 + 1; }
|
||||
|
||||
auto& pawn_entry(const Position& pos) {
|
||||
return pawnHistory[pos.pawn_key() & pawnHistSizeMinus1];
|
||||
}
|
||||
const auto& pawn_entry(const Position& pos) const {
|
||||
return pawnHistory[pos.pawn_key() & pawnHistSizeMinus1];
|
||||
}
|
||||
|
||||
auto& pawn_correction_entry(const Position& pos) {
|
||||
return correctionHistory[pos.pawn_key() & sizeMinus1];
|
||||
}
|
||||
@@ -243,9 +261,11 @@ struct SharedHistories {
|
||||
}
|
||||
|
||||
UnifiedCorrectionHistory correctionHistory;
|
||||
PawnHistory pawnHistory;
|
||||
|
||||
|
||||
private:
|
||||
size_t sizeMinus1;
|
||||
size_t sizeMinus1, pawnHistSizeMinus1;
|
||||
};
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
+3
-3
@@ -87,14 +87,14 @@ MovePicker::MovePicker(const Position& p,
|
||||
const LowPlyHistory* lph,
|
||||
const CapturePieceToHistory* cph,
|
||||
const PieceToHistory** ch,
|
||||
const PawnHistory* ph,
|
||||
const SharedHistories* sh,
|
||||
int pl) :
|
||||
pos(p),
|
||||
mainHistory(mh),
|
||||
lowPlyHistory(lph),
|
||||
captureHistory(cph),
|
||||
continuationHistory(ch),
|
||||
pawnHistory(ph),
|
||||
sharedHistory(sh),
|
||||
ttMove(ttm),
|
||||
depth(d),
|
||||
ply(pl) {
|
||||
@@ -159,7 +159,7 @@ ExtMove* MovePicker::score(MoveList<Type>& ml) {
|
||||
{
|
||||
// histories
|
||||
m.value = 2 * (*mainHistory)[us][m.raw()];
|
||||
m.value += 2 * (*pawnHistory)[pawn_history_index(pos)][pc][to];
|
||||
m.value += 2 * sharedHistory->pawn_entry(pos)[pc][to];
|
||||
m.value += (*continuationHistory[0])[pc][to];
|
||||
m.value += (*continuationHistory[1])[pc][to];
|
||||
m.value += (*continuationHistory[2])[pc][to];
|
||||
|
||||
+2
-2
@@ -45,7 +45,7 @@ class MovePicker {
|
||||
const LowPlyHistory*,
|
||||
const CapturePieceToHistory*,
|
||||
const PieceToHistory**,
|
||||
const PawnHistory*,
|
||||
const SharedHistories*,
|
||||
int);
|
||||
MovePicker(const Position&, Move, int, const CapturePieceToHistory*);
|
||||
Move next_move();
|
||||
@@ -64,7 +64,7 @@ class MovePicker {
|
||||
const LowPlyHistory* lowPlyHistory;
|
||||
const CapturePieceToHistory* captureHistory;
|
||||
const PieceToHistory** continuationHistory;
|
||||
const PawnHistory* pawnHistory;
|
||||
const SharedHistories* sharedHistory;
|
||||
Move ttMove;
|
||||
ExtMove * cur, *endCur, *endBadCaptures, *endCaptures, *endGenerated;
|
||||
int stage;
|
||||
|
||||
@@ -885,6 +885,7 @@ void Position::do_move(Move m,
|
||||
|
||||
if (history)
|
||||
{
|
||||
prefetch(&history->pawn_entry(*this)[pc][to]);
|
||||
prefetch(&history->pawn_correction_entry(*this));
|
||||
prefetch(&history->minor_piece_correction_entry(*this));
|
||||
prefetch(&history->nonpawn_correction_entry<WHITE>(*this));
|
||||
|
||||
+8
-13
@@ -580,14 +580,10 @@ void Search::Worker::undo_null_move(Position& pos) { pos.undo_null_move(); }
|
||||
void Search::Worker::clear() {
|
||||
mainHistory.fill(68);
|
||||
captureHistory.fill(-689);
|
||||
pawnHistory.fill(-1238);
|
||||
|
||||
// Each thread is responsible for clearing their part of shared history
|
||||
size_t len = sharedHistory.get_size() / numaTotal;
|
||||
size_t start = numaThreadIdx * len;
|
||||
size_t end = std::min(start + len, sharedHistory.get_size());
|
||||
|
||||
sharedHistory.correctionHistory.clear_range(start, end);
|
||||
sharedHistory.correctionHistory.clear_range(0, numaThreadIdx);
|
||||
sharedHistory.pawnHistory.clear_range(-1238, numaThreadIdx);
|
||||
|
||||
ttMoveHistory = 0;
|
||||
|
||||
@@ -861,7 +857,7 @@ Value Search::Worker::search(
|
||||
mainHistory[~us][((ss - 1)->currentMove).raw()] << evalDiff * 9;
|
||||
if (!ttHit && type_of(pos.piece_on(prevSq)) != PAWN
|
||||
&& ((ss - 1)->currentMove).type_of() != PROMOTION)
|
||||
pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] << evalDiff * 13;
|
||||
sharedHistory.pawn_entry(pos)[pos.piece_on(prevSq)][prevSq] << evalDiff * 13;
|
||||
}
|
||||
|
||||
|
||||
@@ -992,7 +988,7 @@ moves_loop: // When in check, search starts here
|
||||
|
||||
|
||||
MovePicker mp(pos, ttData.move, depth, &mainHistory, &lowPlyHistory, &captureHistory, contHist,
|
||||
&pawnHistory, ss->ply);
|
||||
&sharedHistory, ss->ply);
|
||||
|
||||
value = bestValue;
|
||||
|
||||
@@ -1081,7 +1077,7 @@ moves_loop: // When in check, search starts here
|
||||
{
|
||||
int history = (*contHist[0])[movedPiece][move.to_sq()]
|
||||
+ (*contHist[1])[movedPiece][move.to_sq()]
|
||||
+ pawnHistory[pawn_history_index(pos)][movedPiece][move.to_sq()];
|
||||
+ sharedHistory.pawn_entry(pos)[movedPiece][move.to_sq()];
|
||||
|
||||
// Continuation history based pruning
|
||||
if (history < -4083 * depth)
|
||||
@@ -1439,7 +1435,7 @@ moves_loop: // When in check, search starts here
|
||||
mainHistory[~us][((ss - 1)->currentMove).raw()] << scaledBonus * 243 / 32768;
|
||||
|
||||
if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION)
|
||||
pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq]
|
||||
sharedHistory.pawn_entry(pos)[pos.piece_on(prevSq)][prevSq]
|
||||
<< scaledBonus * 1160 / 32768;
|
||||
}
|
||||
|
||||
@@ -1611,7 +1607,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
|
||||
// the moves. We presently use two stages of move generator in quiescence search:
|
||||
// captures, or evasions only when in check.
|
||||
MovePicker mp(pos, ttData.move, DEPTH_QS, &mainHistory, &lowPlyHistory, &captureHistory,
|
||||
contHist, &pawnHistory, ss->ply);
|
||||
contHist, &sharedHistory, ss->ply);
|
||||
|
||||
// Step 5. Loop through all pseudo-legal moves until no moves remain or a beta
|
||||
// cutoff occurs.
|
||||
@@ -1900,8 +1896,7 @@ void update_quiet_histories(
|
||||
|
||||
update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 896 / 1024);
|
||||
|
||||
int pIndex = pawn_history_index(pos);
|
||||
workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()]
|
||||
workerThread.sharedHistory.pawn_entry(pos)[pos.moved_piece(move)][move.to_sq()]
|
||||
<< bonus * (bonus > 0 ? 905 : 505) / 1024;
|
||||
}
|
||||
|
||||
|
||||
@@ -292,7 +292,6 @@ class Worker {
|
||||
|
||||
CapturePieceToHistory captureHistory;
|
||||
ContinuationHistory continuationHistory[2][2];
|
||||
PawnHistory pawnHistory;
|
||||
CorrectionHistory<Continuation> continuationCorrectionHistory;
|
||||
|
||||
TTMoveHistory ttMoveHistory;
|
||||
|
||||
Reference in New Issue
Block a user