mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-22 12:47:08 +00:00
Add ability to disable performance-sensitive relaxed atomic operations at compile time
Adds a `RelaxedAtomic` wrapper around either `T` or `std::atomic<T>` and `USE_SLOPPY_ATOMICS` preprocessor define. The intent of this flag is to allow easy disabling of atomics on WASM, where even relaxed atomics are expensive because all atomics have `seq_cst` semantics. Passed non regression STC LLR: 2.99 (-2.94,2.94) <-1.75,0.25> Total: 50624 W: 12976 L: 12776 D: 24872 Ptnml(0-2): 112, 5445, 14005, 5631, 119 https://tests.stockfishchess.org/tests/view/6a1f690e818cacc1db0ad2c7 Passed non-regression STC SMP LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 163696 W: 41514 L: 41438 D: 80744 Ptnml(0-2): 162, 18272, 44904, 18348, 162 https://tests.stockfishchess.org/tests/view/6a21fb97351b79f679cc44b3 Using this class for the TT also allows us to remove the TSAN suppressions, since the UB is fixed. closes https://github.com/official-stockfish/Stockfish/pull/6877 No functional change
This commit is contained in:
committed by
Joost VandeVondele
parent
415ff793a0
commit
0111d11e23
+1
-1
@@ -878,7 +878,7 @@ ifeq ($(lsx),yes)
|
||||
endif
|
||||
|
||||
ifeq ($(arch),wasm32)
|
||||
CXXFLAGS += -pthread -msimd128 -DUSE_POPCNT -DNNUE_EMBEDDING_OFF
|
||||
CXXFLAGS += -pthread -msimd128 -DUSE_POPCNT -DNNUE_EMBEDDING_OFF -DUSE_SLOPPY_ATOMICS
|
||||
LDFLAGS += -pthread -sINITIAL_MEMORY=64MB -sALLOW_MEMORY_GROWTH -sSTACK_SIZE=3MB
|
||||
ifeq ($(relaxedsimd),yes)
|
||||
CXXFLAGS += -mrelaxed-simd
|
||||
|
||||
+3
-19
@@ -57,28 +57,12 @@ struct StatsEntry {
|
||||
static_assert(std::is_arithmetic_v<T>, "Not an arithmetic type");
|
||||
|
||||
private:
|
||||
static constexpr bool Atomic =
|
||||
#ifdef __wasm__
|
||||
false; // don't use atomics on WASM as they are always seq_cst
|
||||
#else
|
||||
Shared;
|
||||
#endif
|
||||
std::conditional_t<Atomic, std::atomic<T>, T> entry;
|
||||
std::conditional_t<Shared, RelaxedAtomic<T>, T> entry;
|
||||
|
||||
public:
|
||||
void operator=(const T& v) {
|
||||
if constexpr (Atomic)
|
||||
entry.store(v, std::memory_order_relaxed);
|
||||
else
|
||||
entry = v;
|
||||
}
|
||||
void operator=(const T& v) { entry = v; }
|
||||
|
||||
operator T() const {
|
||||
if constexpr (Atomic)
|
||||
return entry.load(std::memory_order_relaxed);
|
||||
else
|
||||
return entry;
|
||||
}
|
||||
operator T() const { return entry; }
|
||||
|
||||
void operator<<(int bonus) {
|
||||
// Make sure that bonus is in range [-D, D]
|
||||
|
||||
+97
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
@@ -339,6 +340,102 @@ class MultiArray {
|
||||
constexpr void swap(MultiArray<T, Size, Sizes...>& other) noexcept { data_.swap(other.data_); }
|
||||
};
|
||||
|
||||
// Wrapper around std::atomic<T> which uses relaxed accesses or plain
|
||||
// accesses, depending on the config. Intended use is e.g. wasm where
|
||||
// the overhead of atomic instructions can be significant, and we only
|
||||
// require non-tearing for the updates, while ensuring we use relaxed
|
||||
// accesses otherwise.
|
||||
template<typename T>
|
||||
class RelaxedAtomic {
|
||||
static constexpr bool UseAtomic =
|
||||
#ifdef USE_SLOPPY_ATOMICS
|
||||
!std::atomic<T>::is_always_lock_free || sizeof(T) > sizeof(size_t);
|
||||
#else
|
||||
true;
|
||||
#endif
|
||||
|
||||
public:
|
||||
RelaxedAtomic() = default;
|
||||
RelaxedAtomic(T val) :
|
||||
inner(val) {}
|
||||
RelaxedAtomic(const RelaxedAtomic& a) :
|
||||
inner(static_cast<T>(a)) {}
|
||||
|
||||
T operator=(T val) {
|
||||
if constexpr (UseAtomic)
|
||||
inner.store(val, std::memory_order_relaxed);
|
||||
else
|
||||
inner = val;
|
||||
return val;
|
||||
}
|
||||
|
||||
RelaxedAtomic& operator=(const RelaxedAtomic& a) {
|
||||
this->store(static_cast<T>(a), std::memory_order_relaxed);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator T() const {
|
||||
if constexpr (UseAtomic)
|
||||
return inner.load(std::memory_order_relaxed);
|
||||
else
|
||||
return inner;
|
||||
}
|
||||
|
||||
RelaxedAtomic& operator+=(int val) {
|
||||
T res = this->load(std::memory_order_relaxed) + val;
|
||||
this->store(res, std::memory_order_relaxed);
|
||||
return *this;
|
||||
}
|
||||
|
||||
RelaxedAtomic& operator++() {
|
||||
T res = this->load(std::memory_order_relaxed) + 1;
|
||||
this->store(res, std::memory_order_relaxed);
|
||||
return *this;
|
||||
}
|
||||
|
||||
RelaxedAtomic& operator--() {
|
||||
T res = this->load(std::memory_order_relaxed) - 1;
|
||||
this->store(res, std::memory_order_relaxed);
|
||||
return *this;
|
||||
}
|
||||
|
||||
T operator++(int) {
|
||||
T val = this->load(std::memory_order_relaxed);
|
||||
this->store(val + 1, std::memory_order_relaxed);
|
||||
return val;
|
||||
}
|
||||
|
||||
T operator--(int) {
|
||||
T val = this->load(std::memory_order_relaxed);
|
||||
this->store(val - 1, std::memory_order_relaxed);
|
||||
return val;
|
||||
}
|
||||
|
||||
RelaxedAtomic& operator-=(int val) {
|
||||
T res = this->load(std::memory_order_relaxed) - val;
|
||||
this->store(res, std::memory_order_relaxed);
|
||||
return *this;
|
||||
}
|
||||
|
||||
T load(std::memory_order order) const {
|
||||
assert(order == std::memory_order_relaxed);
|
||||
if constexpr (UseAtomic)
|
||||
return inner.load(order);
|
||||
else
|
||||
return inner;
|
||||
}
|
||||
|
||||
void store(T val, std::memory_order order) {
|
||||
assert(order == std::memory_order_relaxed);
|
||||
if constexpr (UseAtomic)
|
||||
inner.store(val, order);
|
||||
else
|
||||
inner = val;
|
||||
}
|
||||
|
||||
private:
|
||||
std::conditional_t<UseAtomic, std::atomic<T>, T> inner;
|
||||
};
|
||||
|
||||
// xorshift64star Pseudo-Random Number Generator
|
||||
// This class is based on original code written and dedicated
|
||||
|
||||
+2
-4
@@ -623,8 +623,7 @@ 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);
|
||||
// Preferable over fetch_add to avoid locking instructions
|
||||
nodes.store(nodes.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed);
|
||||
++nodes;
|
||||
|
||||
auto [dirtyPiece, dirtyThreats] = accumulatorStack.push();
|
||||
pos.do_move(move, st, givesCheck, dirtyPiece, dirtyThreats, &tt, &sharedHistory);
|
||||
@@ -901,8 +900,7 @@ Value Search::Worker::search(
|
||||
|
||||
if (err != TB::ProbeState::FAIL)
|
||||
{
|
||||
// Preferable over fetch_add to avoid locking instructions
|
||||
tbHits.store(tbHits.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed);
|
||||
++tbHits;
|
||||
|
||||
int drawScore = tbConfig.useRule50 ? 1 : 0;
|
||||
|
||||
|
||||
+2
-2
@@ -375,8 +375,8 @@ class Worker {
|
||||
|
||||
LimitsType limits;
|
||||
|
||||
usize pvIdx, pvLast;
|
||||
std::atomic<u64> nodes, tbHits, bestMoveChanges;
|
||||
size_t pvIdx, pvLast;
|
||||
RelaxedAtomic<u64> nodes, tbHits, bestMoveChanges;
|
||||
int selDepth, nmpMinPly;
|
||||
|
||||
Value optimism[COLOR_NB];
|
||||
|
||||
+2
-2
@@ -26,8 +26,8 @@
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "misc.h"
|
||||
#include "memory.h"
|
||||
#include "misc.h"
|
||||
#include "numa.h"
|
||||
#include "position.h"
|
||||
#include "search.h"
|
||||
@@ -168,7 +168,7 @@ class ThreadPool {
|
||||
std::vector<std::unique_ptr<Thread>> threads;
|
||||
std::vector<NumaIndex> boundThreadToNumaNode;
|
||||
|
||||
u64 accumulate(std::atomic<u64> Search::Worker::* member) const {
|
||||
u64 accumulate(RelaxedAtomic<u64> Search::Worker::* member) const {
|
||||
|
||||
u64 sum = 0;
|
||||
for (auto&& th : threads)
|
||||
|
||||
+7
-7
@@ -79,12 +79,12 @@ struct TTEntry {
|
||||
friend class TranspositionTable;
|
||||
friend struct TTWriter;
|
||||
|
||||
u16 key16;
|
||||
u8 depth8;
|
||||
u8 genBound8;
|
||||
Move move16;
|
||||
i16 value16;
|
||||
i16 eval16;
|
||||
RelaxedAtomic<u16> key16;
|
||||
RelaxedAtomic<u8> depth8;
|
||||
RelaxedAtomic<u8> genBound8;
|
||||
RelaxedAtomic<Move> move16;
|
||||
RelaxedAtomic<i16> value16;
|
||||
RelaxedAtomic<i16> eval16;
|
||||
};
|
||||
|
||||
// Populates the TTEntry with a new node's data, possibly
|
||||
@@ -214,7 +214,7 @@ void TranspositionTable::clear(ThreadPool& threads) {
|
||||
const usize start = stride * i;
|
||||
const usize len = i + 1 != threadCount ? stride : clusterCount - start;
|
||||
|
||||
std::memset(&table[start], 0, len * sizeof(Cluster));
|
||||
std::memset(static_cast<void*>(&table[start]), 0, len * sizeof(Cluster));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import fnmatch
|
||||
|
||||
from testing import (
|
||||
EPD,
|
||||
TSAN,
|
||||
Stockfish as Engine,
|
||||
MiniTestFramework,
|
||||
OrderedClassMembers,
|
||||
@@ -591,7 +590,6 @@ if __name__ == "__main__":
|
||||
args = parse_args()
|
||||
|
||||
EPD.create_bench_epd()
|
||||
TSAN.set_tsan_option()
|
||||
Syzygy.download_syzygy()
|
||||
|
||||
framework = MiniTestFramework()
|
||||
@@ -600,7 +598,6 @@ if __name__ == "__main__":
|
||||
framework.run([TestCLI, TestInteractive, TestSyzygy, TestEnPassantSanitization])
|
||||
|
||||
EPD.delete_bench_epd()
|
||||
TSAN.unset_tsan_option()
|
||||
|
||||
if framework.has_failed():
|
||||
sys.exit(1)
|
||||
|
||||
@@ -43,28 +43,6 @@ class Valgrind:
|
||||
return ["valgrind", "--error-exitcode=42", "--fair-sched=try"]
|
||||
|
||||
|
||||
class TSAN:
|
||||
@staticmethod
|
||||
def set_tsan_option():
|
||||
with open(f"tsan.supp", "w") as f:
|
||||
f.write(
|
||||
"""
|
||||
race:Stockfish::TTEntry::read
|
||||
race:Stockfish::TTEntry::save
|
||||
race:Stockfish::TTWriter::penalize
|
||||
race:Stockfish::TranspositionTable::probe
|
||||
race:Stockfish::TranspositionTable::hashfull
|
||||
"""
|
||||
)
|
||||
|
||||
os.environ["TSAN_OPTIONS"] = "suppressions=./tsan.supp"
|
||||
|
||||
@staticmethod
|
||||
def unset_tsan_option():
|
||||
os.environ.pop("TSAN_OPTIONS", None)
|
||||
os.remove(f"tsan.supp")
|
||||
|
||||
|
||||
class EPD:
|
||||
@staticmethod
|
||||
def create_bench_epd():
|
||||
|
||||
Reference in New Issue
Block a user