From b083049fe0c3671d20d195aa8dea72687cd6e44c Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Mon, 17 Nov 2025 13:24:05 +0100 Subject: [PATCH] Use non-locking operations for nodes count and tbHits fetch_add and friends must produce a locking instruction because they guarantee consistency in the presence of multiple writers. Because only one thread ever writes to nodes and tbHits, we may use relaxed load and store to emit regular loads and stores (on both x86-64 and arm64), while avoiding undefined behavior and miscounting nodes. Credit must go to Arseniy Surkov of Reckless (codedeliveryservice) for uncovering this performance gotcha. failed yellow as a gainer on fishtest: LLR: -2.95 (-2.94,2.94) <0.00,2.00> Total: 219616 W: 57165 L: 57105 D: 105346 Ptnml(0-2): 732, 24277, 59720, 24357, 722 https://tests.stockfishchess.org/tests/view/69086abfea4b268f1fac2809 potential small speedup on large core system: ``` ==== master ==== 1 Nodes/second : 294269736 2 Nodes/second : 295217629 Average (over 2): 294743682 ==== patch ==== 1 Nodes/second : 299003603 2 Nodes/second : 298111320 Average (over 2): 298557461 ``` closes https://github.com/official-stockfish/Stockfish/pull/6392 No functional change --- src/search.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9fb7b702f..53ff9f5d9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -529,7 +529,8 @@ void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, Stac void Search::Worker::do_move( Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss) { bool capture = pos.capture_stage(move); - nodes.fetch_add(1, std::memory_order_relaxed); + // Preferable over fetch_add to avoid locking instructions + nodes.store(nodes.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed); auto [dirtyPiece, dirtyThreats] = accumulatorStack.push(); pos.do_move(move, st, givesCheck, dirtyPiece, dirtyThreats, &tt); @@ -749,7 +750,8 @@ Value Search::Worker::search( if (err != TB::ProbeState::FAIL) { - tbHits.fetch_add(1, std::memory_order_relaxed); + // Preferable over fetch_add to avoid locking instructions + tbHits.store(tbHits.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed); int drawScore = tbConfig.useRule50 ? 1 : 0;