Add PVMoves Struct

Use a dedicated struct for keeping track of the pv moves.

anematode pointed out that we can use the ValueList for this as well, left that open for a future patch.

Passed Non Regression
https://tests.stockfishchess.org/tests/view/69d399cfec2dc6794cf18376
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 267264 W: 68895 L: 68927 D: 129442
Ptnml(0-2): 790, 29238, 73566, 29290, 748

closes https://github.com/official-stockfish/Stockfish/pull/6726

No functional change
This commit is contained in:
Disservin
2026-04-15 20:30:00 +02:00
committed by Joost VandeVondele
parent 4bdeb6352c
commit 45711fceb4
2 changed files with 71 additions and 42 deletions
+14 -25
View File
@@ -128,7 +128,6 @@ void update_correction_history(const Position& pos,
Value value_draw(size_t nodes) { return VALUE_DRAW - 1 + Value(nodes & 0x2); }
Value value_to_tt(Value v, int ply);
Value value_from_tt(Value v, int ply, int r50c);
void update_pv(Move* pv, Move move, const Move* childPv);
void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus);
void update_quiet_histories(
const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus);
@@ -263,7 +262,7 @@ bool Search::Worker::iterative_deepening() {
SearchManager* mainThread = (is_mainthread() ? main_manager() : nullptr);
Move pv[MAX_PLY + 1];
PVMoves pv;
Depth lastBestMoveDepth = 0;
@@ -290,7 +289,7 @@ bool Search::Worker::iterative_deepening() {
for (int i = 0; i <= MAX_PLY + 2; ++i)
(ss + i)->ply = i;
ss->pv = pv;
ss->pv = &pv;
if (mainThread)
{
@@ -652,7 +651,7 @@ Value Search::Worker::search(
assert(0 < depth && depth < MAX_PLY);
assert(!(PvNode && cutNode));
Move pv[MAX_PLY + 1];
PVMoves pv;
StateInfo st;
Key posKey;
@@ -1293,8 +1292,8 @@ moves_loop: // When in check, search starts here
// otherwise let the parent node fail low with value <= alpha and try another move.
if (PvNode && (moveCount == 1 || value > alpha))
{
(ss + 1)->pv = pv;
(ss + 1)->pv[0] = Move::none();
(ss + 1)->pv = &pv;
(ss + 1)->pv->clear();
// Extend move from transposition table if we are about to dive into qsearch.
// decisive score handling improves mate finding and retrograde analysis.
@@ -1353,8 +1352,8 @@ moves_loop: // When in check, search starts here
assert((ss + 1)->pv);
for (Move* m = (ss + 1)->pv; *m != Move::none(); ++m)
rm.pv.push_back(*m);
for (Move pvMove : *(ss + 1)->pv)
rm.pv.push_back(pvMove);
// We record how often the best move has been changed in each iteration.
// This information is used for time management. In MultiPV mode,
@@ -1383,7 +1382,7 @@ moves_loop: // When in check, search starts here
bestMove = move;
if (PvNode && !rootNode) // Update pv even in fail-high case
update_pv(ss->pv, move, (ss + 1)->pv);
ss->pv->update(move, (ss + 1)->pv);
if (value >= beta)
{
@@ -1527,7 +1526,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
return alpha;
}
Move pv[MAX_PLY + 1];
PVMoves pv;
StateInfo st;
Key posKey;
@@ -1539,8 +1538,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
// Step 1. Initialize node
if (PvNode)
{
(ss + 1)->pv = pv;
ss->pv[0] = Move::none();
(ss + 1)->pv = &pv;
ss->pv->clear();
}
bestMove = Move::none();
@@ -1701,7 +1700,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
bestMove = move;
if (PvNode) // Update pv even in fail-high case
update_pv(ss->pv, move, (ss + 1)->pv);
ss->pv->update(move, (ss + 1)->pv);
if (value < beta) // Update alpha here!
alpha = value;
@@ -1817,15 +1816,6 @@ Value value_from_tt(Value v, int ply, int r50c) {
}
// Adds current move and appends child pv[]
void update_pv(Move* pv, Move move, const Move* childPv) {
for (*pv++ = move; childPv && *childPv != Move::none();)
*pv++ = *childPv++;
*pv = Move::none();
}
// Updates stats at the end of search() when a bestMove is found
void update_all_stats(const Position& pos,
Stack* ss,
@@ -1923,7 +1913,6 @@ void update_quiet_histories(
workerThread.sharedHistory.pawn_entry(pos)[pos.moved_piece(move)][move.to_sq()]
<< bonus * (bonus > 0 ? 974 : 543) / 1024;
}
}
// When playing with strength handicap, choose the best move among a set of
@@ -2113,8 +2102,8 @@ void syzygy_extend_pv(const OptionsMap& options,
v = VALUE_DRAW;
// Undo the PV moves
for (auto it = rootMove.pv.rbegin(); it != rootMove.pv.rend(); ++it)
pos.undo_move(*it);
for (size_t i = rootMove.pv.size(); i > 0; --i)
pos.undo_move(rootMove.pv[i - 1]);
// Inform if we couldn't get a full extension in time
if (time_abort())
+57 -17
View File
@@ -23,7 +23,6 @@
#include <array>
#include <atomic>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <map>
@@ -31,6 +30,7 @@
#include <string>
#include <string_view>
#include <vector>
#include <cstring>
#include "history.h"
#include "misc.h"
@@ -58,11 +58,52 @@ class OptionsMap;
namespace Search {
struct PVMoves {
Move moves[MAX_PLY + 1];
std::size_t length = 0;
Move* begin() { return moves; }
const Move* begin() const { return moves; }
Move* end() { return moves + length; }
const Move* end() const { return moves + length; }
Move& operator[](std::size_t index) { return moves[index]; }
const Move& operator[](std::size_t index) const { return moves[index]; }
bool empty() const { return length == 0; }
std::size_t size() const { return length; }
void clear() { length = 0; }
void push_back(Move move) {
assert(length < MAX_PLY + 1);
moves[length++] = move;
}
void resize(std::size_t newSize) {
assert(newSize <= length);
length = newSize;
}
void update(Move move, const PVMoves* childPv) {
assert(childPv == nullptr || childPv->size() <= MAX_PLY);
length = childPv ? childPv->length : 0;
if (childPv)
{
std::memcpy(moves + 1, childPv->moves, length * sizeof(Move));
}
moves[0] = move;
++length;
}
};
// Stack struct keeps track of the information we need to remember from nodes
// shallower and deeper in the tree during the search. Each search thread has
// its own array of Stack objects, indexed by the current ply.
struct Stack {
Move* pv;
PVMoves* pv;
PieceToHistory* continuationHistory;
CorrectionHistory<PieceTo>* continuationCorrectionHistory;
int ply;
@@ -85,8 +126,7 @@ struct Stack {
// fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves.
struct RootMove {
explicit RootMove(Move m) :
pv(1, m) {}
explicit RootMove(Move m) { pv.push_back(m); }
bool extract_ponder_from_tt(const TranspositionTable& tt, Position& pos);
bool operator==(const Move& m) const { return pv[0] == m; }
// Sort in descending order
@@ -94,18 +134,18 @@ struct RootMove {
return m.score != score ? m.score < score : m.previousScore < previousScore;
}
uint64_t effort = 0;
Value score = -VALUE_INFINITE;
Value previousScore = -VALUE_INFINITE;
Value averageScore = -VALUE_INFINITE;
Value meanSquaredScore = -VALUE_INFINITE * VALUE_INFINITE;
Value uciScore = -VALUE_INFINITE;
bool scoreLowerbound = false;
bool scoreUpperbound = false;
int selDepth = 0;
int tbRank = 0;
Value tbScore;
std::vector<Move> pv;
uint64_t effort = 0;
Value score = -VALUE_INFINITE;
Value previousScore = -VALUE_INFINITE;
Value averageScore = -VALUE_INFINITE;
Value meanSquaredScore = -VALUE_INFINITE * VALUE_INFINITE;
Value uciScore = -VALUE_INFINITE;
bool scoreLowerbound = false;
bool scoreUpperbound = false;
int selDepth = 0;
int tbRank = 0;
Value tbScore;
PVMoves pv;
};
using RootMoves = std::vector<RootMove>;
@@ -343,7 +383,7 @@ class Worker {
Depth rootDepth, completedDepth;
Value rootDelta;
std::vector<Move> lastIterationPV;
PVMoves lastIterationPV;
size_t threadIdx, numaThreadIdx, numaTotal;
NumaReplicatedAccessToken numaAccessToken;