mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-24 05:37:13 +00:00
Merge commit '74a8fc060465a822f0c047f908d5fb07ebc6ad96' into cluster
This is the last commit before there are more conflicts.
This commit is contained in:
@@ -26,6 +26,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download required linux packages
|
||||
if: runner.os == 'Linux'
|
||||
@@ -91,4 +92,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }}
|
||||
path: .
|
||||
path: |
|
||||
.
|
||||
!.git
|
||||
!.output
|
||||
|
||||
@@ -19,6 +19,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run clang-format style check
|
||||
uses: jidicula/clang-format-action@f62da5e3d3a2d88ff364771d9d938773a618ab5e # @v4.11.0
|
||||
|
||||
@@ -30,6 +30,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
@@ -25,6 +25,8 @@ jobs:
|
||||
shell: ${{ matrix.config.shell }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install fixed GCC on Linux
|
||||
if: runner.os == 'Linux'
|
||||
@@ -86,4 +88,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }}
|
||||
path: .
|
||||
path: |
|
||||
.
|
||||
!.git
|
||||
!.output
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# This workflow will play games with a debug enabled SF using the PR
|
||||
|
||||
name: Games
|
||||
on:
|
||||
workflow_call:
|
||||
jobs:
|
||||
Matetrack:
|
||||
name: Games
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout SF repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
path: Stockfish
|
||||
persist-credentials: false
|
||||
|
||||
- name: build debug enabled version of SF
|
||||
working-directory: Stockfish/src
|
||||
run: make -j build debug=yes
|
||||
|
||||
- name: Checkout fast-chess repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: Disservin/fast-chess
|
||||
path: fast-chess
|
||||
ref: d54af1910d5479c669dc731f1f54f9108a251951
|
||||
persist-credentials: false
|
||||
|
||||
- name: fast-chess build
|
||||
working-directory: fast-chess
|
||||
run: make -j
|
||||
|
||||
- name: Run games
|
||||
working-directory: fast-chess
|
||||
run: |
|
||||
./fast-chess -rounds 4 -games 2 -repeat -concurrency 4 -openings file=app/tests/data/openings.epd format=epd order=random -srand $RANDOM\
|
||||
-engine name=sf1 cmd=/home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish\
|
||||
-engine name=sf2 cmd=/home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish\
|
||||
-ratinginterval 1 -report penta=true -each proto=uci tc=4+0.04 -log file=fast.log | tee fast.out
|
||||
cat fast.log
|
||||
! grep "Assertion" fast.log > /dev/null
|
||||
! grep "disconnect" fast.out > /dev/null
|
||||
@@ -14,6 +14,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: Stockfish
|
||||
persist-credentials: false
|
||||
|
||||
- name: Checkout include-what-you-use
|
||||
uses: actions/checkout@v4
|
||||
@@ -21,6 +22,7 @@ jobs:
|
||||
repository: include-what-you-use/include-what-you-use
|
||||
ref: f25caa280dc3277c4086ec345ad279a2463fea0f
|
||||
path: include-what-you-use
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download required linux packages
|
||||
run: |
|
||||
|
||||
@@ -13,6 +13,7 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
path: Stockfish
|
||||
persist-credentials: false
|
||||
|
||||
- name: build SF
|
||||
working-directory: Stockfish/src
|
||||
@@ -24,6 +25,7 @@ jobs:
|
||||
repository: vondele/matetrack
|
||||
path: matetrack
|
||||
ref: 20287a1a145f30a166b7ef251eddb611e4e44fbf
|
||||
persist-credentials: false
|
||||
|
||||
- name: matetrack install deps
|
||||
working-directory: matetrack
|
||||
|
||||
@@ -40,6 +40,8 @@ jobs:
|
||||
shell: ${{ matrix.config.shell }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download required linux packages
|
||||
run: |
|
||||
|
||||
@@ -15,8 +15,12 @@ jobs:
|
||||
Prerelease:
|
||||
if: github.repository == 'official-stockfish/Stockfish' && (github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag'))
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # For deleting/creating a prerelease
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# returns null if no pre-release exists
|
||||
- name: Get Commit SHA of Latest Pre-release
|
||||
@@ -66,6 +70,8 @@ jobs:
|
||||
arm_matrix: ${{ steps.set-arm-matrix.outputs.arm_matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- id: set-matrix
|
||||
run: |
|
||||
TASKS=$(echo $(cat .github/ci/matrix.json) )
|
||||
@@ -92,15 +98,25 @@ jobs:
|
||||
uses: ./.github/workflows/tests.yml
|
||||
Matetrack:
|
||||
uses: ./.github/workflows/matetrack.yml
|
||||
Games:
|
||||
uses: ./.github/workflows/games.yml
|
||||
Binaries:
|
||||
if: github.repository == 'official-stockfish/Stockfish'
|
||||
needs: [Matrix, Prerelease, Compilation]
|
||||
uses: ./.github/workflows/upload_binaries.yml
|
||||
with:
|
||||
matrix: ${{ needs.Matrix.outputs.matrix }}
|
||||
permissions:
|
||||
contents: write # For deleting/creating a (pre)release
|
||||
secrets:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
ARM_Binaries:
|
||||
if: github.repository == 'official-stockfish/Stockfish'
|
||||
needs: [Matrix, Prerelease, ARMCompilation]
|
||||
uses: ./.github/workflows/upload_binaries.yml
|
||||
with:
|
||||
matrix: ${{ needs.Matrix.outputs.arm_matrix }}
|
||||
permissions:
|
||||
contents: write # For deleting/creating a (pre)release
|
||||
secrets:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -106,6 +106,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download required linux packages
|
||||
if: runner.os == 'Linux'
|
||||
|
||||
@@ -5,6 +5,9 @@ on:
|
||||
matrix:
|
||||
type: string
|
||||
required: true
|
||||
secrets:
|
||||
token:
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
Artifacts:
|
||||
@@ -25,6 +28,8 @@ jobs:
|
||||
shell: ${{ matrix.config.shell }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download artifact from compilation
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -78,6 +83,7 @@ jobs:
|
||||
uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981
|
||||
with:
|
||||
files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }}
|
||||
token: ${{ secrets.token }}
|
||||
|
||||
- name: Get last commit sha
|
||||
id: last_commit
|
||||
@@ -104,3 +110,4 @@ jobs:
|
||||
tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
|
||||
prerelease: true
|
||||
files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }}
|
||||
token: ${{ secrets.token }}
|
||||
|
||||
+10
-6
@@ -300,16 +300,20 @@ std::string Engine::get_numa_config_as_string() const {
|
||||
|
||||
std::string Engine::numa_config_information_as_string() const {
|
||||
auto cfgStr = get_numa_config_as_string();
|
||||
return "Available Processors: " + cfgStr;
|
||||
return "Available processors: " + cfgStr;
|
||||
}
|
||||
|
||||
std::string Engine::thread_binding_information_as_string() const {
|
||||
auto boundThreadsByNode = get_bound_thread_count_by_numa_node();
|
||||
if (boundThreadsByNode.empty())
|
||||
return "";
|
||||
|
||||
auto boundThreadsByNode = get_bound_thread_count_by_numa_node();
|
||||
std::stringstream ss;
|
||||
ss << "NUMA Node Thread Binding: ";
|
||||
|
||||
size_t threadsSize = threads.size();
|
||||
ss << "Using " << threadsSize << (threadsSize > 1 ? " threads" : " thread");
|
||||
|
||||
if (boundThreadsByNode.empty())
|
||||
return ss.str();
|
||||
|
||||
ss << " with NUMA node thread binding: ";
|
||||
|
||||
bool isFirst = true;
|
||||
|
||||
|
||||
+32
-5
@@ -281,13 +281,21 @@ template<size_t N>
|
||||
struct DebugInfo {
|
||||
std::atomic<int64_t> data[N] = {0};
|
||||
|
||||
constexpr inline std::atomic<int64_t>& operator[](int index) { return data[index]; }
|
||||
constexpr std::atomic<int64_t>& operator[](int index) { return data[index]; }
|
||||
};
|
||||
|
||||
DebugInfo<2> hit[MaxDebugSlots];
|
||||
DebugInfo<2> mean[MaxDebugSlots];
|
||||
DebugInfo<3> stdev[MaxDebugSlots];
|
||||
DebugInfo<6> correl[MaxDebugSlots];
|
||||
struct DebugExtremes: public DebugInfo<3> {
|
||||
DebugExtremes() {
|
||||
data[1] = std::numeric_limits<int64_t>::min();
|
||||
data[2] = std::numeric_limits<int64_t>::max();
|
||||
}
|
||||
};
|
||||
|
||||
DebugInfo<2> hit[MaxDebugSlots];
|
||||
DebugInfo<2> mean[MaxDebugSlots];
|
||||
DebugInfo<3> stdev[MaxDebugSlots];
|
||||
DebugInfo<6> correl[MaxDebugSlots];
|
||||
DebugExtremes extremes[MaxDebugSlots];
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -311,6 +319,18 @@ void dbg_stdev_of(int64_t value, int slot) {
|
||||
stdev[slot][2] += value * value;
|
||||
}
|
||||
|
||||
void dbg_extremes_of(int64_t value, int slot) {
|
||||
++extremes[slot][0];
|
||||
|
||||
int64_t current_max = extremes[slot][1].load();
|
||||
while (current_max < value && !extremes[slot][1].compare_exchange_weak(current_max, value))
|
||||
{}
|
||||
|
||||
int64_t current_min = extremes[slot][2].load();
|
||||
while (current_min > value && !extremes[slot][2].compare_exchange_weak(current_min, value))
|
||||
{}
|
||||
}
|
||||
|
||||
void dbg_correl_of(int64_t value1, int64_t value2, int slot) {
|
||||
|
||||
++correl[slot][0];
|
||||
@@ -345,6 +365,13 @@ void dbg_print() {
|
||||
std::cerr << "Stdev #" << i << ": Total " << n << " Stdev " << r << std::endl;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MaxDebugSlots; ++i)
|
||||
if ((n = extremes[i][0]))
|
||||
{
|
||||
std::cerr << "Extremity #" << i << ": Total " << n << " Min " << extremes[i][2]
|
||||
<< " Max " << extremes[i][1] << std::endl;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MaxDebugSlots; ++i)
|
||||
if ((n = correl[i][0]))
|
||||
{
|
||||
|
||||
@@ -67,6 +67,8 @@ std::optional<std::string> read_file_to_string(const std::string& path);
|
||||
void dbg_hit_on(bool cond, int slot = 0);
|
||||
void dbg_mean_of(int64_t value, int slot = 0);
|
||||
void dbg_stdev_of(int64_t value, int slot = 0);
|
||||
void dbg_extremes_of(int64_t value, int slot);
|
||||
|
||||
void dbg_correl_of(int64_t value1, int64_t value2, int slot = 0);
|
||||
void dbg_print();
|
||||
|
||||
|
||||
+10
-10
@@ -1156,9 +1156,9 @@ bool Position::has_repeated() const {
|
||||
}
|
||||
|
||||
|
||||
// Tests if the position has a move which draws by repetition,
|
||||
// or an earlier position has a move that directly reaches the current position.
|
||||
bool Position::has_game_cycle(int ply) const {
|
||||
// Tests if the position has a move which draws by repetition.
|
||||
// This function accurately matches the outcome of is_draw() over all legal moves.
|
||||
bool Position::upcoming_repetition(int ply) const {
|
||||
|
||||
int j;
|
||||
|
||||
@@ -1169,10 +1169,16 @@ bool Position::has_game_cycle(int ply) const {
|
||||
|
||||
Key originalKey = st->key;
|
||||
StateInfo* stp = st->previous;
|
||||
Key other = originalKey ^ stp->key ^ Zobrist::side;
|
||||
|
||||
for (int i = 3; i <= end; i += 2)
|
||||
{
|
||||
stp = stp->previous->previous;
|
||||
stp = stp->previous;
|
||||
other ^= stp->key ^ stp->previous->key ^ Zobrist::side;
|
||||
stp = stp->previous;
|
||||
|
||||
if (other != 0)
|
||||
continue;
|
||||
|
||||
Key moveKey = originalKey ^ stp->key;
|
||||
if ((j = H1(moveKey), cuckoo[j] == moveKey) || (j = H2(moveKey), cuckoo[j] == moveKey))
|
||||
@@ -1188,12 +1194,6 @@ bool Position::has_game_cycle(int ply) const {
|
||||
|
||||
// For nodes before or at the root, check that the move is a
|
||||
// repetition rather than a move to the current position.
|
||||
// In the cuckoo table, both moves Rc1c5 and Rc5c1 are stored in
|
||||
// the same location, so we have to select which square to check.
|
||||
if (color_of(piece_on(empty(s1) ? s2 : s1)) != side_to_move())
|
||||
continue;
|
||||
|
||||
// For repetitions before or at the root, require one more
|
||||
if (stp->repetition)
|
||||
return true;
|
||||
}
|
||||
|
||||
+1
-1
@@ -156,7 +156,7 @@ class Position {
|
||||
int game_ply() const;
|
||||
bool is_chess960() const;
|
||||
bool is_draw(int ply) const;
|
||||
bool has_game_cycle(int ply) const;
|
||||
bool upcoming_repetition(int ply) const;
|
||||
bool has_repeated() const;
|
||||
int rule50_count() const;
|
||||
Value non_pawn_material(Color c) const;
|
||||
|
||||
+68
-32
@@ -63,7 +63,7 @@ static constexpr double EvalLevel[10] = {0.981, 0.956, 0.895, 0.949, 0.913,
|
||||
Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) {
|
||||
Value futilityMult = 109 - 40 * noTtCutNode;
|
||||
Value improvingDeduction = 59 * improving * futilityMult / 32;
|
||||
Value worseningDeduction = 328 * oppWorsening * futilityMult / 1024;
|
||||
Value worseningDeduction = oppWorsening * futilityMult / 3;
|
||||
|
||||
return futilityMult * d - improvingDeduction - worseningDeduction;
|
||||
}
|
||||
@@ -550,7 +550,7 @@ void Search::Worker::iterative_deepening() {
|
||||
void Search::Worker::clear() {
|
||||
counterMoves.fill(Move::none());
|
||||
mainHistory.fill(0);
|
||||
captureHistory.fill(0);
|
||||
captureHistory.fill(-700);
|
||||
pawnHistory.fill(-1193);
|
||||
correctionHistory.fill(0);
|
||||
|
||||
@@ -582,9 +582,8 @@ Value Search::Worker::search(
|
||||
// Limit the depth if extensions made it too large
|
||||
depth = std::min(depth, MAX_PLY - 1);
|
||||
|
||||
// Check if we have an upcoming move that draws by repetition, or
|
||||
// if the opponent had an alternative move earlier to this position.
|
||||
if (!rootNode && alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply))
|
||||
// Check if we have an upcoming move that draws by repetition.
|
||||
if (!rootNode && alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply))
|
||||
{
|
||||
alpha = value_draw(this->nodes);
|
||||
if (alpha >= beta)
|
||||
@@ -800,8 +799,7 @@ Value Search::Worker::search(
|
||||
// Use static evaluation difference to improve quiet move ordering (~9 Elo)
|
||||
if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture)
|
||||
{
|
||||
int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1590, 1371);
|
||||
bonus = bonus > 0 ? 2 * bonus : bonus / 2;
|
||||
int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1590, 1371) + 800;
|
||||
thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus;
|
||||
if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION)
|
||||
thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq]
|
||||
@@ -835,8 +833,9 @@ Value Search::Worker::search(
|
||||
&& eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening)
|
||||
- (ss - 1)->statScore / 263
|
||||
>= beta
|
||||
&& eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttData.move || ttCapture))
|
||||
return beta > VALUE_TB_LOSS_IN_MAX_PLY ? beta + (eval - beta) / 3 : eval;
|
||||
&& eval >= beta && (!ttData.move || ttCapture) && beta > VALUE_TB_LOSS_IN_MAX_PLY
|
||||
&& eval < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
return beta + (eval - beta) / 3;
|
||||
|
||||
// Step 9. Null move search with verification search (~35 Elo)
|
||||
if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 14369
|
||||
@@ -880,9 +879,12 @@ Value Search::Worker::search(
|
||||
}
|
||||
|
||||
// Step 10. Internal iterative reductions (~9 Elo)
|
||||
// For PV nodes without a ttMove, we decrease depth by 3.
|
||||
// For PV nodes without a ttMove, we decrease depth.
|
||||
// Additionally, if the current position is found in the TT
|
||||
// and the stored depth in the TT is greater than or equal to
|
||||
// current search depth, we decrease search depth even further.
|
||||
if (PvNode && !ttData.move)
|
||||
depth -= 3;
|
||||
depth -= 3 + (ss->ttHit && ttData.depth >= depth);
|
||||
|
||||
// Use qsearch if depth <= 0.
|
||||
if (depth <= 0)
|
||||
@@ -909,12 +911,19 @@ Value Search::Worker::search(
|
||||
assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta);
|
||||
|
||||
MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &thisThread->captureHistory);
|
||||
Move probcutCapturesSearched[32];
|
||||
int probcutCaptureCount = 0;
|
||||
Piece captured;
|
||||
|
||||
while ((move = mp.next_move()) != Move::none())
|
||||
if (move != excludedMove && pos.legal(move))
|
||||
{
|
||||
assert(pos.capture_stage(move));
|
||||
|
||||
movedPiece = pos.moved_piece(move);
|
||||
captured = pos.piece_on(move.to_sq());
|
||||
|
||||
|
||||
// Prefetch the TT entry for the resulting position
|
||||
prefetch(tt.first_entry(pos.key_after(move)));
|
||||
|
||||
@@ -938,6 +947,19 @@ Value Search::Worker::search(
|
||||
|
||||
if (value >= probCutBeta)
|
||||
{
|
||||
thisThread->captureHistory[movedPiece][move.to_sq()][type_of(captured)]
|
||||
<< stat_bonus(depth - 2);
|
||||
|
||||
for (int i = 0; i < probcutCaptureCount; i++)
|
||||
{
|
||||
movedPiece = pos.moved_piece(probcutCapturesSearched[i]);
|
||||
captured = pos.piece_on(probcutCapturesSearched[i].to_sq());
|
||||
|
||||
thisThread->captureHistory[movedPiece][probcutCapturesSearched[i].to_sq()]
|
||||
[type_of(captured)]
|
||||
<< -stat_malus(depth - 3);
|
||||
}
|
||||
|
||||
// Save ProbCut data into transposition table
|
||||
Distributed::save(tt, threads, thisThread, ttWriter, posKey,
|
||||
value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3,
|
||||
@@ -945,6 +967,9 @@ Value Search::Worker::search(
|
||||
return std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY ? value - (probCutBeta - beta)
|
||||
: value;
|
||||
}
|
||||
|
||||
if (probcutCaptureCount < 32)
|
||||
probcutCapturesSearched[probcutCaptureCount++] = move;
|
||||
}
|
||||
|
||||
Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable);
|
||||
@@ -954,9 +979,8 @@ moves_loop: // When in check, search starts here
|
||||
|
||||
// Step 12. A small Probcut idea, when we are in check (~4 Elo)
|
||||
probCutBeta = beta + 388;
|
||||
if (ss->inCheck && !PvNode && ttCapture && (ttData.bound & BOUND_LOWER)
|
||||
&& ttData.depth >= depth - 4 && ttData.value >= probCutBeta
|
||||
&& std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY
|
||||
if (ss->inCheck && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4
|
||||
&& ttData.value >= probCutBeta && std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY
|
||||
&& std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
return probCutBeta;
|
||||
|
||||
@@ -1042,7 +1066,7 @@ moves_loop: // When in check, search starts here
|
||||
// Futility pruning for captures (~2 Elo)
|
||||
if (!givesCheck && lmrDepth < 7 && !ss->inCheck)
|
||||
{
|
||||
Value futilityValue = ss->staticEval + 287 + 248 * lmrDepth
|
||||
Value futilityValue = ss->staticEval + 294 + 246 * lmrDepth
|
||||
+ PieceValue[capturedPiece] + captHist / 7;
|
||||
if (futilityValue <= alpha)
|
||||
continue;
|
||||
@@ -1050,7 +1074,7 @@ moves_loop: // When in check, search starts here
|
||||
|
||||
// SEE based pruning for captures and checks (~11 Elo)
|
||||
int seeHist = std::clamp(captHist / 32, -180 * depth, 163 * depth);
|
||||
if (!pos.see_ge(move, -160 * depth - seeHist))
|
||||
if (!pos.see_ge(move, -163 * depth - seeHist))
|
||||
continue;
|
||||
}
|
||||
else
|
||||
@@ -1061,15 +1085,15 @@ moves_loop: // When in check, search starts here
|
||||
+ thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()];
|
||||
|
||||
// Continuation history based pruning (~2 Elo)
|
||||
if (lmrDepth < 6 && history < -4151 * depth)
|
||||
if (lmrDepth < 6 && history < -3899 * depth)
|
||||
continue;
|
||||
|
||||
history += 2 * thisThread->mainHistory[us][move.from_to()];
|
||||
|
||||
lmrDepth += history / 3678;
|
||||
lmrDepth += history / 4040;
|
||||
|
||||
Value futilityValue =
|
||||
ss->staticEval + (bestValue < ss->staticEval - 51 ? 138 : 54) + 140 * lmrDepth;
|
||||
ss->staticEval + (bestValue < ss->staticEval - 51 ? 135 : 56) + 140 * lmrDepth;
|
||||
|
||||
// Futility pruning: parent node (~13 Elo)
|
||||
if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha)
|
||||
@@ -1135,8 +1159,8 @@ moves_loop: // When in check, search starts here
|
||||
// and if after excluding the ttMove with a reduced search we fail high over the original beta,
|
||||
// we assume this expected cut-node is not singular (multiple moves fail high),
|
||||
// and we can prune the whole subtree by returning a softbound.
|
||||
else if (singularBeta >= beta)
|
||||
return singularBeta;
|
||||
else if (value >= beta && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
return value;
|
||||
|
||||
// Negative extensions
|
||||
// If other moves failed high over (ttValue - margin) without the ttMove on a reduced search,
|
||||
@@ -1185,7 +1209,8 @@ moves_loop: // When in check, search starts here
|
||||
|
||||
// Decrease reduction if position is or has been on the PV (~7 Elo)
|
||||
if (ss->ttPv)
|
||||
r -= 1 + (ttData.value > alpha) + (ttData.depth >= depth);
|
||||
r -= 1 + (ttData.value > alpha) + (ttData.depth >= depth)
|
||||
- (PvNode && ttData.value < alpha && ttData.depth >= depth);
|
||||
|
||||
// Decrease reduction for PvNodes (~0 Elo on STC, ~2 Elo on LTC)
|
||||
if (PvNode)
|
||||
@@ -1204,7 +1229,7 @@ moves_loop: // When in check, search starts here
|
||||
|
||||
// Increase reduction if next ply has a lot of fail high (~5 Elo)
|
||||
if ((ss + 1)->cutoffCnt > 3)
|
||||
r++;
|
||||
r += 1 + !(PvNode || cutNode);
|
||||
|
||||
// For first picked move (ttMove) reduce reduction
|
||||
// but never allow it to go below 0 (~3 Elo)
|
||||
@@ -1293,7 +1318,7 @@ moves_loop: // When in check, search starts here
|
||||
rm.effort += nodes - nodeCount;
|
||||
|
||||
rm.averageScore =
|
||||
rm.averageScore != -VALUE_INFINITE ? (2 * value + rm.averageScore) / 3 : value;
|
||||
rm.averageScore != -VALUE_INFINITE ? (value + rm.averageScore) / 2 : value;
|
||||
|
||||
// PV move or new best move?
|
||||
if (moveCount == 1 || value > alpha)
|
||||
@@ -1333,11 +1358,17 @@ moves_loop: // When in check, search starts here
|
||||
rm.score = -VALUE_INFINITE;
|
||||
}
|
||||
|
||||
if (value > bestValue)
|
||||
// In case we have an alternative move equal in eval to the current bestmove,
|
||||
// promote it to bestmove by pretending it just exceeds alpha (but not beta).
|
||||
int inc = (value == bestValue && (int(nodes) & 15) == 0
|
||||
&& ss->ply + 2 + ss->ply / 32 >= thisThread->rootDepth
|
||||
&& std::abs(value) + 1 < VALUE_TB_WIN_IN_MAX_PLY);
|
||||
|
||||
if (value + inc > bestValue)
|
||||
{
|
||||
bestValue = value;
|
||||
|
||||
if (value > alpha)
|
||||
if (value + inc > alpha)
|
||||
{
|
||||
bestMove = move;
|
||||
|
||||
@@ -1396,10 +1427,16 @@ moves_loop: // When in check, search starts here
|
||||
// Bonus for prior countermove that caused the fail low
|
||||
else if (!priorCapture && prevSq != SQ_NONE)
|
||||
{
|
||||
int bonus = (113 * (depth > 5) + 118 * (PvNode || cutNode)
|
||||
+ 191 * ((ss - 1)->statScore < -14396) + 119 * ((ss - 1)->moveCount > 8)
|
||||
int bonus = (113 * (depth > 5) + 118 * (PvNode || cutNode) + 119 * ((ss - 1)->moveCount > 8)
|
||||
+ 64 * (!ss->inCheck && bestValue <= ss->staticEval - 107)
|
||||
+ 147 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75));
|
||||
|
||||
|
||||
// proportional to "how much damage we have to undo"
|
||||
if ((ss - 1)->statScore < -8000)
|
||||
bonus += std::clamp(-(ss - 1)->statScore / 100, 0, 250);
|
||||
|
||||
|
||||
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq,
|
||||
stat_bonus(depth) * bonus / 100);
|
||||
thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()]
|
||||
@@ -1461,9 +1498,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
|
||||
assert(PvNode || (alpha == beta - 1));
|
||||
assert(depth <= 0);
|
||||
|
||||
// Check if we have an upcoming move that draws by repetition, or if
|
||||
// the opponent had an alternative move earlier to this position. (~1 Elo)
|
||||
if (alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply))
|
||||
// Check if we have an upcoming move that draws by repetition. (~1 Elo)
|
||||
if (alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply))
|
||||
{
|
||||
alpha = value_draw(this->nodes);
|
||||
if (alpha >= beta)
|
||||
@@ -1633,7 +1669,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
|
||||
|
||||
// If static exchange evaluation is much worse than what is needed to not
|
||||
// fall below alpha we can prune this move.
|
||||
if (futilityBase > alpha && !pos.see_ge(move, (alpha - futilityBase) * 2 - 30))
|
||||
if (futilityBase > alpha && !pos.see_ge(move, (alpha - futilityBase) * 4))
|
||||
{
|
||||
bestValue = alpha;
|
||||
continue;
|
||||
|
||||
+3
-3
@@ -40,10 +40,10 @@ namespace Stockfish {
|
||||
class OptionsMap;
|
||||
using Value = int;
|
||||
|
||||
// Sometimes we don't want to actually bind the threads, but the recipent still
|
||||
// Sometimes we don't want to actually bind the threads, but the recipient still
|
||||
// needs to think it runs on *some* NUMA node, such that it can access structures
|
||||
// that rely on NUMA node knowledge. This class encapsulates this optional process
|
||||
// such that the recipent does not need to know whether the binding happened or not.
|
||||
// such that the recipient does not need to know whether the binding happened or not.
|
||||
class OptionalThreadToNumaNodeBinder {
|
||||
public:
|
||||
OptionalThreadToNumaNodeBinder(NumaIndex n) :
|
||||
@@ -88,7 +88,7 @@ class Thread {
|
||||
// this name is no longer correct. However, this class (and ThreadPool)
|
||||
// require further work to make them properly generic while maintaining
|
||||
// appropriate specificity regarding search, from the point of view of an
|
||||
// outside user, so renaming of this function in left for whenever that happens.
|
||||
// outside user, so renaming of this function is left for whenever that happens.
|
||||
void wait_for_search_finished();
|
||||
size_t id() const { return idx; }
|
||||
|
||||
|
||||
+6
-4
@@ -55,6 +55,7 @@ struct TTEntry {
|
||||
Bound(genBound8 & 0x3), bool(genBound8 & 0x4)};
|
||||
}
|
||||
|
||||
bool is_occupied() const;
|
||||
void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8);
|
||||
// The returned age is a multiple of TranspositionTable::GENERATION_DELTA
|
||||
uint8_t relative_age(const uint8_t generation8) const;
|
||||
@@ -84,7 +85,8 @@ static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF;
|
||||
|
||||
// DEPTH_ENTRY_OFFSET exists because 1) we use `bool(depth8)` as the occupancy check, but
|
||||
// 2) we need to store negative depths for QS. (`depth8` is the only field with "spare bits":
|
||||
// we sacrifice the ability to store depths greater than 1<<8 less the offset, as asserted below.)
|
||||
// we sacrifice the ability to store depths greater than 1<<8 less the offset, as asserted in `save`.)
|
||||
bool TTEntry::is_occupied() const { return bool(depth8); }
|
||||
|
||||
// Populates the TTEntry with a new node's data, possibly
|
||||
// overwriting an old position. The update is not atomic and can be racy.
|
||||
@@ -196,7 +198,7 @@ int TranspositionTable::hashfull() const {
|
||||
int cnt = 0;
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
for (int j = 0; j < ClusterSize; ++j)
|
||||
cnt += table[i].entry[j].depth8
|
||||
cnt += table[i].entry[j].is_occupied()
|
||||
&& (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8;
|
||||
|
||||
return cnt / ClusterSize;
|
||||
@@ -227,7 +229,7 @@ std::tuple<bool, TTData, TTWriter> TranspositionTable::probe(const Key key) cons
|
||||
if (tte[i].key16 == key16)
|
||||
// This gap is the main place for read races.
|
||||
// After `read()` completes that copy is final, but may be self-inconsistent.
|
||||
return {bool(tte[i].depth8), tte[i].read(), TTWriter(&tte[i])};
|
||||
return {tte[i].is_occupied(), tte[i].read(), TTWriter(&tte[i])};
|
||||
|
||||
// Find an entry to be replaced according to the replacement strategy
|
||||
TTEntry* replace = tte;
|
||||
@@ -236,7 +238,7 @@ std::tuple<bool, TTData, TTWriter> TranspositionTable::probe(const Key key) cons
|
||||
> tte[i].depth8 - tte[i].relative_age(generation8) * 2)
|
||||
replace = &tte[i];
|
||||
|
||||
return {false, replace->read(), TTWriter(replace)};
|
||||
return {false, TTData(), TTWriter(replace)};
|
||||
}
|
||||
|
||||
|
||||
|
||||
+5
-3
@@ -110,16 +110,18 @@ void UCIEngine::loop() {
|
||||
sync_cout << "id name " << engine_info(true) << "\n"
|
||||
<< engine.get_options() << sync_endl;
|
||||
|
||||
print_info_string(engine.numa_config_information_as_string());
|
||||
print_info_string(engine.thread_binding_information_as_string());
|
||||
|
||||
sync_cout << "uciok" << sync_endl;
|
||||
}
|
||||
|
||||
else if (token == "setoption")
|
||||
setoption(is);
|
||||
else if (token == "go")
|
||||
{
|
||||
// send info strings after the go command is sent for old GUIs and python-chess
|
||||
print_info_string(engine.numa_config_information_as_string());
|
||||
print_info_string(engine.thread_binding_information_as_string());
|
||||
go(is);
|
||||
}
|
||||
else if (token == "position")
|
||||
position(is);
|
||||
else if (token == "ucinewgame")
|
||||
|
||||
Reference in New Issue
Block a user