Compare commits

...
Author SHA1 Message Date
Joost VandeVondele cb3d4ee9b4 Stockfish 18
Official release version of Stockfish 18

Bench: 2050811

---

Stockfish 18

Today, we have the pleasure of announcing Stockfish 18, a new major release. As
always, you can freely download it at [stockfishchess.org/download][1] and use
it as a drop-in replacement in the [GUI of your choice][2] to benefit from
stronger play and more accurate analysis.

Whether you can spare hours or days of CPU time, your help matters for the
ongoing development of Stockfish. Find out how you can help at
[stockfishchess.org/get-involved][3]. Join our [Discord server][4] to get in
touch with the community of developers and users of the project!

Quality of chess play

In tests against Stockfish 17, this new release brings an Elo gain of up to [46
points][5], and wins [four times as many game pairs][6] as it loses. Quality
improved throughout, including in [Fischer Random Chess][7].

Stockfish is stronger than any human, even when running on older or low-end
hardware. On the highest-end hardware, where Stockfish can search over [500
million positions per second][8], it continues to [dominate chess engine
tournaments][9].

Update highlights

Next-Generation Evaluation

This release introduces the SFNNv10 network architecture. The network’s input
layer has been augmented with 'Threat Inputs' features as part of a massive
community effort. These features allow the engine to 'see' which pieces are
threatened more naturally, resulting in more accurate evaluations.

Hardware and Performance Optimizations

A key highlight is the new 'Shared Memory' implementation, which allows
different Stockfish processes to share the same memory space for neural network
weights. This makes it the most efficient version for cloud analysis and
high-concurrency testing.

Significant efforts have also been made to utilize hardware more effectively by
adapting the code to make use of modern CPU instructions and refining how
threads interact during a search.

Search Improvements

Stockfish 18 features a heavily refined search, utilizing 'Correction History'
to dynamically adjust evaluations based on patterns found during the search
itself. These and other refinements allow the engine to detect stalemates and
evaluate fortresses significantly better than previous versions. A particularly
rare issue, involving threefold repetition detection, en passant, and pins, was
also fixed.

Refactored Training Workflow

The training of Stockfish's neural networks has transitioned to an automated
and reproducible model. This new framework allows the project to employ
standardized recipes to chain complex training stages together. This transition
facilitates the training of networks using over 100 billion positions of [Lc0
evaluation data][10].

Thank you

In this release in particular, we are deeply grateful to the contributors who
shared their research and ideas to help develop the new threat-input network
architecture.

The Stockfish project builds on a thriving community of enthusiasts (thanks to
everybody!) who contribute their expertise, time, and resources to build a free
and open-source chess engine that is robust, widely available, and very strong.

We would like to express our gratitude for the [14.6k stars][11] that light up
our GitHub project. Thank you for your support and encouragement – your
recognition means a lot to us. Programmers can contribute to the project either
directly to [Stockfish][12] (C++), to [Fishtest][13] (HTML, CSS, JavaScript,
and Python), to our trainer [nnue-pytorch][14] (C++ and Python), or to our
[website][15] (HTML, CSS/SCSS, and JavaScript).

The Stockfish team

[1]: https://stockfishchess.org/download
[2]: https://official-stockfish.github.io/docs/stockfish-wiki/Download-and-usage.html#download-a-chess-gui
[3]: https://stockfishchess.org/get-involved/
[4]: https://discord.gg/GWDRS3kU6R
[5]: https://tests.stockfishchess.org/tests/view/696a9e15cec152c6220c1d19
[6]: https://tests.stockfishchess.org/tests/view/696a9e4dcec152c6220c1d1b
[7]: https://tests.stockfishchess.org/tests/view/696a9e83cec152c6220c1d1d
[8]: https://openbenchmarking.org/test/pts/stockfish
[9]: https://en.wikipedia.org/wiki/Stockfish_(chess)#Competition_results
[10]: https://lczero.org/blog/2021/06/the-importance-of-open-data/
[11]: https://github.com/official-stockfish/Stockfish/stargazers
[12]: https://github.com/official-stockfish/Stockfish
[13]: https://github.com/official-stockfish/fishtest
[14]: https://github.com/official-stockfish/nnue-pytorch
[15]: https://github.com/official-stockfish/stockfish-web
2026-01-31 12:46:49 +01:00
anematodeandDisservin 253aaefbc0 Fix compilation on BSD/macOS
fixes github.com/official-stockfish/Stockfish/issues/6571

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

No functional change
2026-01-28 20:28:27 +01:00
anematodeandJoost VandeVondele c0f245303b Make PGO builds deterministic again
some environment dependent execution (e.g. pid) were being std::hash'ed, and
net filenames put in unordered maps.  Also uses sprintf instead of
std::to_string.  Depending on precise content, this could lead to different
PGO'ed binaries. This is mitigated by using a basic hash function.

This also fixes a potential issue in net filename generation, in cases where
std::hash would use invocation dependent salt, which is not the cases today for
typical std libraries.

Closes https://github.com/official-stockfish/Stockfish/pull/6562

No functional change
2026-01-22 19:15:17 +01:00
anematodeandJoost VandeVondele f61d4317a3 use default signal handler after cleanup
With the current setup, on Linux, SIGILL (and SIGSEGV/SIGBUS etc., in the case
of a program bug) lead to no feedback if they occur after the signal handlers
are installed, they just exit silently. By invoking the default signal handler,
we can still get the appropriate feedback. This is particularly important for
feedback if someone downloads the wrong SF binary and runs it on Linux.

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

No functional change
2026-01-18 08:53:06 +01:00
Robert Nurnberg @ elitebookandJoost VandeVondele 71f53b92c7 update the WDL model
This PR updates the internal WDL model, using data from 3.1M games played by
the revisions since 44d5467. Note that the normalizing constant increases only
moderately from 377 to 385.

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

No functional change
2026-01-15 22:19:50 +01:00
anematodeandJoost VandeVondele 5b8b304ebd Skip munmap when exiting via a signal
avoid munmap of memory when exiting via signal, which avoids side effects such
as triggering asserts or (caught) segfaults while the process exists.

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

No functional change
2026-01-15 22:17:14 +01:00
Joost VandeVondele 0b9068d510 Fix integer overflow.
scaledBonus can reach rather large values,
which lead to an int overflow as detected anematode using ubsan.
(see https://github.com/official-stockfish/Stockfish/issues/6505#issuecomment-3696988889)

It can be fixed by scaling nominator and denominator appropriately,
which doesn't change the bench, as long as there is no overflow.

First overflow/bench change happens at depth 26

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

Bench: 2050811
2026-01-15 22:13:26 +01:00
Timothy HerchenandJoost VandeVondele eb5a65aeb0 Fix RelationCache on Windows 10 compiles
Windows 10 is missing the GroupMasks and GroupCount members, this breaks compiles on Windows 10.
Windows 11 builds, including the official ones, run fine on Windows 10/11.
To support developers/testers on Windows 10, fallback conditionally to the Windows 10 struct definition.

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

No functional change
2026-01-11 09:13:37 +01:00
KazAppsandJoost VandeVondele e9b2864579 Simplify make_index
Refactor index LUT construction to simplify make_index.

Passed STC Non-Regression:
LLR: 2.93 (-2.94,2.94) <-1.75,0.25>
Total: 62432 W: 16193 L: 16006 D: 30233
Ptnml(0-2): 189, 6950, 16764, 7111, 202
https://tests.stockfishchess.org/tests/view/6959985ad844c1ce7cc7eac8

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

No functional change.
2026-01-11 09:09:55 +01:00
KazAppsandJoost VandeVondele d852a9195e Make enums unsigned
Speed up by using unsigned enums.

Passed STC:
LLR: 2.98 (-2.94,2.94) <0.00,2.00>
Total: 49248 W: 12894 L: 12568 D: 23786
Ptnml(0-2): 119, 5353, 13397, 5593, 16
https://tests.stockfishchess.org/tests/view/695e3e5002d0182a589fe965

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

No functional change
2026-01-10 16:06:18 +01:00
FauziAkramandJoost VandeVondele 9b8c5c9f75 Simplify fail high reduction formula
Passed STC:
LLR: 2.97 (-2.94,2.94) <-1.75,0.25>
Total: 165792 W: 42849 L: 42768 D: 80175
Ptnml(0-2): 512, 19499, 42800, 19566, 519
https://tests.stockfishchess.org/tests/view/695cdd95912b7ff140de60c2

Passed LTC:
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 80448 W: 20619 L: 20459 D: 39370
Ptnml(0-2): 47, 8693, 22596, 8829, 59
https://tests.stockfishchess.org/tests/view/695f7e84ca95f52e4b852536

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

bench: 2050811
2026-01-10 15:47:11 +01:00
anematodeandJoost VandeVondele b4d4eecfb2 Make shared history allocation aware of non-uniform cache access
Although shared history has been successful overall, it led to some speed
issues with large numbers of threads. Originally we just split by NUMA node,
but on systems with non-unified L3 caches (most AMD workstation and server
CPUs, and some Intel E-core based server CPUs), this can still lead to a speed
penalty at the default config. Thus, we decided to further subdivide the shared
history based on the L3 cache structure.

Based on this test, the original SPRTs, and speed experiments, we decided that
grouping L3 domains to reach 32 threads per SharedHistories was a reasonable
balance for affected systems – but we may revisit this in the future. See the
PR for full details.

In an extreme case, a single-socket EPYC 9755 configured with 1 numa domain per socket,
the nps increases from:
Nodes/second               : 182827480
to
Nodes/second               : 229118365

In many cases, when L3 caches are shared between many threads, or when several
numa nodes are already configured per socket, this patch does not influence the
default. This default setting can adjusted with the existing NumaPolicy option.

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

No functional change.
2026-01-10 15:46:01 +01:00
DisservinandJoost VandeVondele 1928ef9571 Compiler Check
Compiles and Runs Stockfish on all supported gcc & clang compilers.
Only linux and avx2 currently.

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

No functional change
2026-01-10 15:36:52 +01:00
DisservinandJoost VandeVondele 5d5e795746 Fix Compiler Warning
Only the one on line 158 is actually required but doesn't hurt to add constexpr where applicable here.

Warning was

"comparison of unsigned expression in '< 0' is always false"

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

No functional change
2026-01-10 15:34:37 +01:00
DisservinandJoost VandeVondele d39bfb61a2 Fix Clang Tbprobe Miscompilation
Recent changes to the Square enum (reducing it from int32_t to int8_t)
now allow the compiler to vectorize loops that were previously too wide
for targets below AVX-512. However, this vectorization which Clang
performs is not correct and causes a miscompilation.

Disable this vectorization.

This particular issue was noticable with Clang 15 and Clang 19,
on avx2 as well as applie-silicon.

Ref: #6063
Original Clang Issue: llvm/llvm-project#80494

First reported by #6528, though misinterpreted.

closes #6529

No functional change
2026-01-10 15:33:36 +01:00
Jakub CiolekandJoost VandeVondele c27c1747e3 qsearch: prevent bestValue from going down
The bestValue can sometimes go down. This happens 2% of the time or so.
This fix stops it from decreasing.

Failed gainer STC:
LLR: -2.94 (-2.94,2.94) <0.00,2.00>
Total: 146176 W: 37930 L: 37976 D: 70270
Ptnml(0-2): 480, 17422, 37366, 17304, 516
https://tests.stockfishchess.org/tests/view/6953be19572093c1986da66a

Passed Non-regression LTC:
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 257796 W: 65662 L: 65683 D: 126451
Ptnml(0-2): 164, 28247, 72087, 28246, 154
https://tests.stockfishchess.org/tests/view/69554ff0d844c1ce7cc7e333

closes https://github.com/official-stockfish/Stockfish/pull/6520
fixes https://github.com/official-stockfish/Stockfish/issues/6519

Bench: 2477446
2026-01-06 12:08:08 +01:00
anematodeandJoost VandeVondele 8be6b14218 Network loading refactoring
closes https://github.com/official-stockfish/Stockfish/pull/6523

No functional change
2026-01-06 12:02:37 +01:00
Syine MinetaandJoost VandeVondele d678f839d8 Fix remote access bug across NUMA nodes
Ensure that thread-local data is created within the correct NUMA
context, so that thread stacks or thread-local storage are allocated
to proper NUMA nodes.

refs https://github.com/official-stockfish/Stockfish/issues/6516

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

No functional change
2026-01-06 12:01:01 +01:00
23 changed files with 661 additions and 323 deletions
+85
View File
@@ -0,0 +1,85 @@
name: AVX2 Compiler Matrix
on:
workflow_call:
jobs:
avx2-compiler-matrix:
name: avx2 (${{ matrix.name }})
runs-on: ubuntu-latest
container:
image: ${{ matrix.image }}
strategy:
fail-fast: false
matrix:
include:
- { name: gcc-10, comp: gcc, cxx: g++, image: "gcc:10" }
- { name: gcc-11, comp: gcc, cxx: g++, image: "gcc:11" }
- { name: gcc-12, comp: gcc, cxx: g++, image: "gcc:12" }
- { name: gcc-13, comp: gcc, cxx: g++, image: "gcc:13" }
- { name: gcc-14, comp: gcc, cxx: g++, image: "gcc:14" }
- { name: gcc-15, comp: gcc, cxx: g++, image: "gcc:15" }
# Using silkeh/clang for older versions
- { name: clang-10, comp: clang, cxx: clang++, image: "silkeh/clang:10", is_clang: true, ver: "10" }
- { name: clang-11, comp: clang, cxx: clang++, image: "silkeh/clang:11", is_clang: true, ver: "11" }
- { name: clang-12, comp: clang, cxx: clang++, image: "silkeh/clang:12", is_clang: true, ver: "12" }
- { name: clang-13, comp: clang, cxx: clang++, image: "silkeh/clang:13", is_clang: true, ver: "13" }
- { name: clang-14, comp: clang, cxx: clang++, image: "silkeh/clang:14", is_clang: true, ver: "14" }
- { name: clang-15, comp: clang, cxx: clang++, image: "silkeh/clang:15", is_clang: true, ver: "15" }
- { name: clang-16, comp: clang, cxx: clang++, image: "silkeh/clang:16", is_clang: true, ver: "16" }
- { name: clang-17, comp: clang, cxx: clang++, image: "silkeh/clang:17", is_clang: true, ver: "17" }
- { name: clang-18, comp: clang, cxx: clang++-18, image: "ubuntu:rolling", is_clang: true, ver: "18" }
- { name: clang-19, comp: clang, cxx: clang++-19, image: "ubuntu:rolling", is_clang: true, ver: "19" }
- { name: clang-20, comp: clang, cxx: clang++-20, image: "ubuntu:rolling", is_clang: true, ver: "20" }
- { name: clang-21, comp: clang, cxx: clang++-21, image: "ubuntu:rolling", is_clang: true, ver: "21" }
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: |
if grep -q "buster" /etc/os-release; then
echo "Debian Buster detected. Switching to archive repositories..."
echo "deb http://archive.debian.org/debian buster main contrib non-free" > /etc/apt/sources.list
echo "deb http://archive.debian.org/debian-security buster/updates main contrib non-free" >> /etc/apt/sources.list
echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99-ignore-valid-until
fi
apt-get update
apt-get install -y curl git make
- name: Set up Clang
if: ${{ matrix.is_clang && matrix.image == 'ubuntu:rolling' }}
run: |
if [ "${{ matrix.ver }}" -le 20 ]; then
apt-get install -y clang-${{ matrix.ver }}
else
apt-get install -y \
clang-${{ matrix.ver }} \
llvm-${{ matrix.ver }}-dev \
llvm-${{ matrix.ver }}-linker-tools \
lld-${{ matrix.ver }}
fi
- name: Download network
working-directory: src
run: make net
- name: Build avx2 binary
working-directory: src
run: |
export CXXFLAGS="-Werror"
if [ "${{ matrix.ver }}" -ge 20 ]; then
export LDFLAGS="-fuse-ld=lld"
apt install -y lld
fi
make clean
make -j build ARCH=x86-64-avx2 COMP=${{ matrix.comp }} COMPCXX=${{ matrix.cxx }}
- name: Smoke test
working-directory: src
run: ./stockfish bench 16 1 6
+2
View File
@@ -120,3 +120,5 @@ jobs:
contents: write # For deleting/creating a (pre)release contents: write # For deleting/creating a (pre)release
secrets: secrets:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
CompilerCheck:
uses: ./.github/workflows/avx2_compilers.yml
+1
View File
@@ -111,6 +111,7 @@ Hongzhi Cheng
Ivan Ivec (IIvec) Ivan Ivec (IIvec)
Jacques B. (Timshel) Jacques B. (Timshel)
Jake Senne (w1wwwwww) Jake Senne (w1wwwwww)
Jakub Ciolek (jake-ciolek)
Jan Ondruš (hxim) Jan Ondruš (hxim)
Jared Kish (Kurtbusch, kurt22i) Jared Kish (Kurtbusch, kurt22i)
Jarrod Torriero (DU-jdto) Jarrod Torriero (DU-jdto)
+4 -1
View File
@@ -49,12 +49,15 @@ std::string Bitboards::pretty(Bitboard b) {
std::string s = "+---+---+---+---+---+---+---+---+\n"; std::string s = "+---+---+---+---+---+---+---+---+\n";
for (Rank r = RANK_8; r >= RANK_1; --r) for (Rank r = RANK_8;; --r)
{ {
for (File f = FILE_A; f <= FILE_H; ++f) for (File f = FILE_A; f <= FILE_H; ++f)
s += b & make_square(f, r) ? "| X " : "| "; s += b & make_square(f, r) ? "| X " : "| ";
s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n"; s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n";
if (r == RANK_1)
break;
} }
s += " a b c d e f g h\n"; s += " a b c d e f g h\n";
+13 -11
View File
@@ -52,19 +52,21 @@ constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq
constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
int MaxThreads = std::max(1024, 4 * int(get_hardware_concurrency())); int MaxThreads = std::max(1024, 4 * int(get_hardware_concurrency()));
// The default configuration will attempt to group L3 domains up to 32 threads.
// This size was found to be a good balance between the Elo gain of increased
// history sharing and the speed loss from more cross-cache accesses (see
// PR#6526). The user can always explicitly override this behavior.
constexpr NumaAutoPolicy DefaultNumaPolicy = BundledL3Policy{32};
Engine::Engine(std::optional<std::string> path) : Engine::Engine(std::optional<std::string> path) :
binaryDirectory(path ? CommandLine::get_binary_directory(*path) : ""), binaryDirectory(path ? CommandLine::get_binary_directory(*path) : ""),
numaContext(NumaConfig::from_system()), numaContext(NumaConfig::from_system(DefaultNumaPolicy)),
states(new std::deque<StateInfo>(1)), states(new std::deque<StateInfo>(1)),
threads(), threads(),
networks( networks(numaContext,
numaContext, // Heap-allocate because sizeof(NN::Networks) is large
// Heap-allocate because sizeof(NN::Networks) is large std::make_unique<NN::Networks>(NN::EvalFile{EvalFileDefaultNameBig, "None", ""},
std::make_unique<NN::Networks>( NN::EvalFile{EvalFileDefaultNameSmall, "None", ""})) {
std::make_unique<NN::NetworkBig>(NN::EvalFile{EvalFileDefaultNameBig, "None", ""},
NN::EmbeddedNNUEType::BIG),
std::make_unique<NN::NetworkSmall>(NN::EvalFile{EvalFileDefaultNameSmall, "None", ""},
NN::EmbeddedNNUEType::SMALL))) {
pos.set(StartFEN, false, &states->back()); pos.set(StartFEN, false, &states->back());
@@ -217,12 +219,12 @@ void Engine::set_position(const std::string& fen, const std::vector<std::string>
void Engine::set_numa_config_from_option(const std::string& o) { void Engine::set_numa_config_from_option(const std::string& o) {
if (o == "auto" || o == "system") if (o == "auto" || o == "system")
{ {
numaContext.set_numa_config(NumaConfig::from_system()); numaContext.set_numa_config(NumaConfig::from_system(DefaultNumaPolicy));
} }
else if (o == "hardware") else if (o == "hardware")
{ {
// Don't respect affinity set in the system. // Don't respect affinity set in the system.
numaContext.set_numa_config(NumaConfig::from_system(false)); numaContext.set_numa_config(NumaConfig::from_system(DefaultNumaPolicy, false));
} }
else if (o == "none") else if (o == "none")
{ {
+1 -1
View File
@@ -40,7 +40,7 @@ namespace Stockfish {
namespace { namespace {
// Version number or dev. // Version number or dev.
constexpr std::string_view version = "dev"; constexpr std::string_view version = "18";
// Our fancy logging facility. The trick here is to replace cin.rdbuf() and // Our fancy logging facility. The trick here is to replace cin.rdbuf() and
// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We // cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We
+28 -13
View File
@@ -34,6 +34,7 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <type_traits>
#include <vector> #include <vector>
#define stringify2(x) #x #define stringify2(x) #x
@@ -305,24 +306,38 @@ inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
#endif #endif
} }
inline std::uint64_t hash_bytes(const char* data, std::size_t size) {
template<typename T> // FNV-1a 64-bit
inline void hash_combine(std::size_t& seed, const T& v) { const char* p = data;
std::hash<T> hasher; std::uint64_t h = 14695981039346656037ull;
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); for (std::size_t i = 0; i < size; ++i)
} h = (h ^ p[i]) * 1099511628211ull;
return h;
template<>
inline void hash_combine(std::size_t& seed, const std::size_t& v) {
seed ^= v + 0x9e3779b9 + (seed << 6) + (seed >> 2);
} }
template<typename T> template<typename T>
inline std::size_t get_raw_data_hash(const T& value) { inline std::size_t get_raw_data_hash(const T& value) {
return std::hash<std::string_view>{}( // We must have no padding bytes because we're reinterpreting as char
std::string_view(reinterpret_cast<const char*>(&value), sizeof(value))); static_assert(std::has_unique_object_representations<T>());
return static_cast<std::size_t>(
hash_bytes(reinterpret_cast<const char*>(&value), sizeof(value)));
} }
template<typename T>
inline void hash_combine(std::size_t& seed, const T& v) {
std::size_t x;
// For primitive types we avoid using the default hasher, which may be
// nondeterministic across program invocations
if constexpr (std::is_integral<T>())
x = v;
else
x = std::hash<T>{}(v);
seed ^= x + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
inline std::uint64_t hash_string(const std::string& sv) { return hash_bytes(sv.data(), sv.size()); }
template<std::size_t Capacity> template<std::size_t Capacity>
class FixedString { class FixedString {
public: public:
@@ -450,7 +465,7 @@ void move_to_front(std::vector<T>& vec, Predicate pred) {
template<std::size_t N> template<std::size_t N>
struct std::hash<Stockfish::FixedString<N>> { struct std::hash<Stockfish::FixedString<N>> {
std::size_t operator()(const Stockfish::FixedString<N>& fstr) const noexcept { std::size_t operator()(const Stockfish::FixedString<N>& fstr) const noexcept {
return std::hash<std::string_view>{}((std::string_view) fstr); return Stockfish::hash_bytes(fstr.data(), fstr.size());
} }
}; };
+4 -4
View File
@@ -47,10 +47,10 @@ template<Direction offset>
inline Move* splat_pawn_moves(Move* moveList, Bitboard to_bb) { inline Move* splat_pawn_moves(Move* moveList, Bitboard to_bb) {
alignas(64) static constexpr auto SPLAT_TABLE = [] { alignas(64) static constexpr auto SPLAT_TABLE = [] {
std::array<Move, 64> table{}; std::array<Move, 64> table{};
for (int8_t i = 0; i < 64; i++) for (int i = 0; i < 64; i++)
{ {
Square from{std::clamp<int8_t>(i - offset, 0, 63)}; Square from{uint8_t(std::clamp(i - offset, 0, 63))};
table[i] = {Move(from, Square{i})}; table[i] = {Move(from, Square{uint8_t(i)})};
} }
return table; return table;
}(); }();
@@ -68,7 +68,7 @@ inline Move* splat_pawn_moves(Move* moveList, Bitboard to_bb) {
inline Move* splat_moves(Move* moveList, Square from, Bitboard to_bb) { inline Move* splat_moves(Move* moveList, Square from, Bitboard to_bb) {
alignas(64) static constexpr auto SPLAT_TABLE = [] { alignas(64) static constexpr auto SPLAT_TABLE = [] {
std::array<Move, 64> table{}; std::array<Move, 64> table{};
for (int8_t i = 0; i < 64; i++) for (uint8_t i = 0; i < 64; i++)
table[i] = {Move(SQUARE_ZERO, Square{i})}; table[i] = {Move(SQUARE_ZERO, Square{i})};
return table; return table;
}(); }();
+9 -35
View File
@@ -37,26 +37,6 @@ struct HelperOffsets {
int cumulativePieceOffset, cumulativeOffset; int cumulativePieceOffset, cumulativeOffset;
}; };
// Information on a particular pair of pieces and whether they should be excluded
struct PiecePairData {
// Layout: bits 8..31 are the index contribution of this piece pair, bits 0 and 1 are exclusion info
uint32_t data;
constexpr PiecePairData() :
data(0) {}
constexpr PiecePairData(bool excluded_pair,
bool semi_excluded_pair,
IndexType feature_index_base) :
data((uint32_t(excluded_pair) << 1) | (uint32_t(semi_excluded_pair && !excluded_pair))
| (uint32_t(feature_index_base) << 8)) {}
// lsb: excluded if from < to; 2nd lsb: always excluded
constexpr uint8_t excluded_pair_info() const { return static_cast<uint8_t>(data); }
constexpr IndexType feature_index_base() const { return static_cast<IndexType>(data >> 8); }
};
constexpr std::array<Piece, 12> AllPieces = { constexpr std::array<Piece, 12> AllPieces = {
W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,
@@ -173,7 +153,7 @@ constexpr auto helper_offsets = init_threat_offsets().first;
constexpr auto offsets = init_threat_offsets().second; constexpr auto offsets = init_threat_offsets().second;
constexpr auto init_index_luts() { constexpr auto init_index_luts() {
std::array<std::array<PiecePairData, PIECE_NB>, PIECE_NB> indices{}; std::array<std::array<std::array<uint32_t, 2>, PIECE_NB>, PIECE_NB> indices{};
for (Piece attacker : AllPieces) for (Piece attacker : AllPieces)
{ {
@@ -189,8 +169,10 @@ constexpr auto init_index_luts() {
+ (color_of(attacked) * (numValidTargets[attacker] / 2) + map) + (color_of(attacked) * (numValidTargets[attacker] / 2) + map)
* helper_offsets[attacker].cumulativePieceOffset; * helper_offsets[attacker].cumulativePieceOffset;
bool excluded = map < 0; bool excluded = map < 0;
indices[attacker][attacked] = PiecePairData(excluded, semi_excluded, feature); indices[attacker][attacked][0] = excluded ? FullThreats::Dimensions : feature;
indices[attacker][attacked][1] =
excluded || semi_excluded ? FullThreats::Dimensions : feature;
} }
} }
@@ -200,7 +182,7 @@ constexpr auto init_index_luts() {
// The final index is calculated from summing data found in these two LUTs, as well // The final index is calculated from summing data found in these two LUTs, as well
// as offsets[attacker][from] // as offsets[attacker][from]
// [attacker][attacked] // [attacker][attacked][from < to]
constexpr auto index_lut1 = init_index_luts(); constexpr auto index_lut1 = init_index_luts();
// [attacker][from][to] // [attacker][from][to]
constexpr auto index_lut2 = index_lut2_array(); constexpr auto index_lut2 = index_lut2_array();
@@ -216,17 +198,9 @@ inline sf_always_inline IndexType FullThreats::make_index(
unsigned attacker_oriented = attacker ^ swap; unsigned attacker_oriented = attacker ^ swap;
unsigned attacked_oriented = attacked ^ swap; unsigned attacked_oriented = attacked ^ swap;
const auto piecePairData = index_lut1[attacker_oriented][attacked_oriented]; return index_lut1[attacker_oriented][attacked_oriented][from_oriented < to_oriented]
+ offsets[attacker_oriented][from_oriented]
const bool less_than = from_oriented < to_oriented; + index_lut2[attacker_oriented][from_oriented][to_oriented];
if ((piecePairData.excluded_pair_info() + less_than) & 2)
return FullThreats::Dimensions;
const IndexType index = piecePairData.feature_index_base()
+ offsets[attacker_oriented][from_oriented]
+ index_lut2[attacker_oriented][from_oriented][to_oriented];
sf_assume(index < Dimensions);
return index;
} }
// Get a list of indices for active features in ascending order // Get a list of indices for active features in ascending order
+3 -4
View File
@@ -28,7 +28,6 @@
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <tuple> #include <tuple>
#include <utility>
#include "../misc.h" #include "../misc.h"
#include "../types.h" #include "../types.h"
@@ -130,9 +129,9 @@ using NetworkSmall = Network<SmallNetworkArchitecture, SmallFeatureTransformer>;
struct Networks { struct Networks {
Networks(std::unique_ptr<NetworkBig>&& nB, std::unique_ptr<NetworkSmall>&& nS) : Networks(EvalFile bigFile, EvalFile smallFile) :
big(std::move(*nB)), big(bigFile, EmbeddedNNUEType::BIG),
small(std::move(*nS)) {} small(smallFile, EmbeddedNNUEType::SMALL) {}
NetworkBig big; NetworkBig big;
NetworkSmall small; NetworkSmall small;
+40 -36
View File
@@ -169,52 +169,56 @@ inline void write_little_endian(std::ostream& stream, const IntType* values, std
write_little_endian<IntType>(stream, values[i]); write_little_endian<IntType>(stream, values[i]);
} }
// Read N signed integers from the stream s, putting them in the array out. // Read N signed integers from the stream s, putting them in the array out.
// The stream is assumed to be compressed using the signed LEB128 format. // The stream is assumed to be compressed using the signed LEB128 format.
// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. // See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme.
template<typename IntType, std::size_t Count> template<typename BufType, typename IntType, std::size_t Count>
inline void read_leb_128(std::istream& stream, std::array<IntType, Count>& out) { inline void read_leb_128_detail(std::istream& stream,
std::array<IntType, Count>& out,
std::uint32_t& bytes_left,
BufType& buf,
std::uint32_t& buf_pos) {
static_assert(std::is_signed_v<IntType>, "Not implemented for unsigned types");
static_assert(sizeof(IntType) <= 4, "Not implemented for types larger than 32 bit");
IntType result = 0;
size_t shift = 0, i = 0;
while (i < Count)
{
if (buf_pos == buf.size())
{
stream.read(reinterpret_cast<char*>(buf.data()),
std::min(std::size_t(bytes_left), buf.size()));
buf_pos = 0;
}
std::uint8_t byte = buf[buf_pos++];
--bytes_left;
result |= (byte & 0x7f) << (shift % 32);
shift += 7;
if ((byte & 0x80) == 0)
{
out[i++] = (shift >= 32 || (byte & 0x40) == 0) ? result : result | ~((1 << shift) - 1);
result = 0;
shift = 0;
}
}
}
template<typename... Arrays>
inline void read_leb_128(std::istream& stream, Arrays&... outs) {
// Check the presence of our LEB128 magic string // Check the presence of our LEB128 magic string
char leb128MagicString[Leb128MagicStringSize]; char leb128MagicString[Leb128MagicStringSize];
stream.read(leb128MagicString, Leb128MagicStringSize); stream.read(leb128MagicString, Leb128MagicStringSize);
assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0); assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0);
static_assert(std::is_signed_v<IntType>, "Not implemented for unsigned types"); auto bytes_left = read_little_endian<std::uint32_t>(stream);
std::array<std::uint8_t, 8192> buf;
std::uint32_t buf_pos = buf.size();
const std::uint32_t BUF_SIZE = 4096; (read_leb_128_detail(stream, outs, bytes_left, buf, buf_pos), ...);
std::uint8_t buf[BUF_SIZE];
auto bytes_left = read_little_endian<std::uint32_t>(stream);
std::uint32_t buf_pos = BUF_SIZE;
for (std::size_t i = 0; i < Count; ++i)
{
IntType result = 0;
size_t shift = 0;
do
{
if (buf_pos == BUF_SIZE)
{
stream.read(reinterpret_cast<char*>(buf), std::min(bytes_left, BUF_SIZE));
buf_pos = 0;
}
std::uint8_t byte = buf[buf_pos++];
--bytes_left;
result |= (byte & 0x7f) << shift;
shift += 7;
if ((byte & 0x80) == 0)
{
out[i] = (sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0)
? result
: result | ~((1 << shift) - 1);
break;
}
} while (shift < sizeof(IntType) * 8);
}
assert(bytes_left == 0); assert(bytes_left == 0);
} }
+14 -28
View File
@@ -132,7 +132,7 @@ class FeatureTransformer {
permute<16>(biases, PackusEpi16Order); permute<16>(biases, PackusEpi16Order);
permute<16>(weights, PackusEpi16Order); permute<16>(weights, PackusEpi16Order);
if (UseThreats) if constexpr (UseThreats)
permute<8>(threatWeights, PackusEpi16Order); permute<8>(threatWeights, PackusEpi16Order);
} }
@@ -140,7 +140,7 @@ class FeatureTransformer {
permute<16>(biases, InversePackusEpi16Order); permute<16>(biases, InversePackusEpi16Order);
permute<16>(weights, InversePackusEpi16Order); permute<16>(weights, InversePackusEpi16Order);
if (UseThreats) if constexpr (UseThreats)
permute<8>(threatWeights, InversePackusEpi16Order); permute<8>(threatWeights, InversePackusEpi16Order);
} }
@@ -152,40 +152,26 @@ class FeatureTransformer {
} }
// Read network parameters // Read network parameters
// TODO: This is ugly. Currently LEB128 on the entire L1 necessitates
// reading the weights into a combined array, and then splitting.
bool read_parameters(std::istream& stream) { bool read_parameters(std::istream& stream) {
read_leb_128<BiasType>(stream, biases); read_leb_128(stream, biases);
if (UseThreats) if constexpr (UseThreats)
{ {
read_little_endian<ThreatWeightType>(stream, threatWeights.data(), read_little_endian<ThreatWeightType>(stream, threatWeights.data(),
ThreatInputDimensions * HalfDimensions); ThreatInputDimensions * HalfDimensions);
read_leb_128<WeightType>(stream, weights); read_leb_128(stream, weights);
auto combinedPsqtWeights = read_leb_128(stream, threatPsqtWeights, psqtWeights);
std::make_unique<std::array<PSQTWeightType, TotalInputDimensions * PSQTBuckets>>();
read_leb_128<PSQTWeightType>(stream, *combinedPsqtWeights);
std::copy(combinedPsqtWeights->begin(),
combinedPsqtWeights->begin() + ThreatInputDimensions * PSQTBuckets,
std::begin(threatPsqtWeights));
std::copy(combinedPsqtWeights->begin() + ThreatInputDimensions * PSQTBuckets,
combinedPsqtWeights->begin()
+ (ThreatInputDimensions + InputDimensions) * PSQTBuckets,
std::begin(psqtWeights));
} }
else else
{ {
read_leb_128<WeightType>(stream, weights); read_leb_128(stream, weights);
read_leb_128<PSQTWeightType>(stream, psqtWeights); read_leb_128(stream, psqtWeights);
} }
permute_weights(); permute_weights();
if (!UseThreats) if constexpr (!UseThreats)
scale_weights(true); scale_weights(true);
return !stream.fail(); return !stream.fail();
@@ -197,12 +183,12 @@ class FeatureTransformer {
copy->unpermute_weights(); copy->unpermute_weights();
if (!UseThreats) if constexpr (!UseThreats)
copy->scale_weights(false); copy->scale_weights(false);
write_leb_128<BiasType>(stream, copy->biases); write_leb_128<BiasType>(stream, copy->biases);
if (UseThreats) if constexpr (UseThreats)
{ {
write_little_endian<ThreatWeightType>(stream, copy->threatWeights.data(), write_little_endian<ThreatWeightType>(stream, copy->threatWeights.data(),
ThreatInputDimensions * HalfDimensions); ThreatInputDimensions * HalfDimensions);
@@ -256,7 +242,7 @@ class FeatureTransformer {
auto psqt = auto psqt =
(psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]); (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]);
if (UseThreats) if constexpr (UseThreats)
{ {
const auto& threatPsqtAccumulation = const auto& threatPsqtAccumulation =
(threatAccumulatorState.acc<HalfDimensions>()).psqtAccumulation; (threatAccumulatorState.acc<HalfDimensions>()).psqtAccumulation;
@@ -348,7 +334,7 @@ class FeatureTransformer {
#else #else
6; 6;
#endif #endif
if (UseThreats) if constexpr (UseThreats)
{ {
const vec_t* tin0 = const vec_t* tin0 =
reinterpret_cast<const vec_t*>(&(threatAccumulation[perspectives[p]][0])); reinterpret_cast<const vec_t*>(&(threatAccumulation[perspectives[p]][0]));
@@ -400,7 +386,7 @@ class FeatureTransformer {
BiasType sum1 = BiasType sum1 =
accumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2]; accumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2];
if (UseThreats) if constexpr (UseThreats)
{ {
BiasType sum0t = threatAccumulation[static_cast<int>(perspectives[p])][j + 0]; BiasType sum0t = threatAccumulation[static_cast<int>(perspectives[p])][j + 0];
BiasType sum1t = BiasType sum1t =
+334 -98
View File
@@ -371,6 +371,50 @@ inline WindowsAffinity get_process_affinity() {
return affinity; return affinity;
} }
// Type machinery used to emulate Cache->GroupCount
template<typename T, typename = void>
struct HasGroupCount: std::false_type {};
template<typename T>
struct HasGroupCount<T, std::void_t<decltype(std::declval<T>().Cache.GroupCount)>>: std::true_type {
};
template<typename T, typename Pred, std::enable_if_t<HasGroupCount<T>::value, bool> = true>
std::set<CpuIndex> readCacheMembers(const T* info, Pred&& is_cpu_allowed) {
std::set<CpuIndex> cpus;
// On Windows 10 this will read a 0 because GroupCount doesn't exist
int groupCount = std::max(info->Cache.GroupCount, WORD(1));
for (WORD procGroup = 0; procGroup < groupCount; ++procGroup)
{
for (BYTE number = 0; number < WIN_PROCESSOR_GROUP_SIZE; ++number)
{
WORD groupNumber = info->Cache.GroupMasks[procGroup].Group;
const CpuIndex c = static_cast<CpuIndex>(groupNumber) * WIN_PROCESSOR_GROUP_SIZE
+ static_cast<CpuIndex>(number);
if (!(info->Cache.GroupMasks[procGroup].Mask & (1ULL << number)) || !is_cpu_allowed(c))
continue;
cpus.insert(c);
}
}
return cpus;
}
template<typename T, typename Pred, std::enable_if_t<!HasGroupCount<T>::value, bool> = true>
std::set<CpuIndex> readCacheMembers(const T* info, Pred&& is_cpu_allowed) {
std::set<CpuIndex> cpus;
for (BYTE number = 0; number < WIN_PROCESSOR_GROUP_SIZE; ++number)
{
WORD groupNumber = info->Cache.GroupMask.Group;
const CpuIndex c = static_cast<CpuIndex>(groupNumber) * WIN_PROCESSOR_GROUP_SIZE
+ static_cast<CpuIndex>(number);
if (!(info->Cache.GroupMask.Mask & (1ULL << number)) || !is_cpu_allowed(c))
continue;
cpus.insert(c);
}
return cpus;
}
#endif #endif
#if defined(__linux__) && !defined(__ANDROID__) #if defined(__linux__) && !defined(__ANDROID__)
@@ -447,14 +491,39 @@ class NumaReplicatedAccessToken {
NumaIndex n; NumaIndex n;
}; };
struct L3Domain {
NumaIndex systemNumaIndex{};
std::set<CpuIndex> cpus{};
};
// Use system NUMA nodes
struct SystemNumaPolicy {};
// Use system-reported L3 domains
struct L3DomainsPolicy {};
// Group system-reported L3 domains until they reach bundleSize
struct BundledL3Policy {
size_t bundleSize;
};
using NumaAutoPolicy = std::variant<SystemNumaPolicy, L3DomainsPolicy, BundledL3Policy>;
// Designed as immutable, because there is no good reason to alter an already // Designed as immutable, because there is no good reason to alter an already
// existing config in a way that doesn't require recreating it completely, and // existing config in a way that doesn't require recreating it completely, and
// it would be complex and expensive to maintain class invariants. // it would be complex and expensive to maintain class invariants.
// The CPU (processor) numbers always correspond to the actual numbering used // The CPU (processor) numbers always correspond to the actual numbering used
// by the system. The NUMA node numbers MAY NOT correspond to the system's // by the system. The NUMA node numbers MAY NOT correspond to the system's
// numbering of the NUMA nodes. In particular, empty nodes may be removed, or // numbering of the NUMA nodes. In particular, by default, if the processor has
// the user may create custom nodes. It is guaranteed that NUMA nodes are NOT // non-uniform cache access within a NUMA node (i.e., a non-unified L3 cache structure),
// empty: every node exposed by NumaConfig has at least one processor assigned. // then L3 domains within a system NUMA node will be used to subdivide it
// into multiple logical NUMA nodes in the config. Additionally, empty nodes may
// be removed, or the user may create custom nodes.
//
// As a special case, when performing system-wide replication of read-only data
// (i.e., LazyNumaReplicatedSystemWide), the system NUMA node is used, rather than
// custom or L3-aware nodes. See that class's get_discriminator() function.
//
// It is guaranteed that NUMA nodes are NOT empty: every node exposed by NumaConfig
// has at least one processor assigned.
// //
// We use startup affinities so as not to modify its own behaviour in time. // We use startup affinities so as not to modify its own behaviour in time.
// //
@@ -469,78 +538,19 @@ class NumaConfig {
add_cpu_range_to_node(NumaIndex{0}, CpuIndex{0}, numCpus - 1); add_cpu_range_to_node(NumaIndex{0}, CpuIndex{0}, numCpus - 1);
} }
// This function queries the system for the mapping of processors to NUMA nodes. // This function gets a NumaConfig based on the system's provided information.
// On Linux we read from standardized kernel sysfs, with a fallback to single NUMA // The available policies are documented above.
// node. On Windows we utilize GetNumaProcessorNodeEx, which has its quirks, see static NumaConfig from_system([[maybe_unused]] const NumaAutoPolicy& policy,
// comment for Windows implementation of get_process_affinity. bool respectProcessAffinity = true) {
static NumaConfig from_system([[maybe_unused]] bool respectProcessAffinity = true) {
NumaConfig cfg = empty(); NumaConfig cfg = empty();
#if defined(__linux__) && !defined(__ANDROID__) #if !((defined(__linux__) && !defined(__ANDROID__)) || defined(_WIN64))
// Fallback for unsupported systems.
for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c)
cfg.add_cpu_to_node(NumaIndex{0}, c);
#else
std::set<CpuIndex> allowedCpus; #if defined(_WIN64)
if (respectProcessAffinity)
allowedCpus = STARTUP_PROCESSOR_AFFINITY;
auto is_cpu_allowed = [respectProcessAffinity, &allowedCpus](CpuIndex c) {
return !respectProcessAffinity || allowedCpus.count(c) == 1;
};
// On Linux things are straightforward, since there's no processor groups and
// any thread can be scheduled on all processors.
// We try to gather this information from the sysfs first
// https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-devices-node
bool useFallback = false;
auto fallback = [&]() {
useFallback = true;
cfg = empty();
};
// /sys/devices/system/node/online contains information about active NUMA nodes
auto nodeIdsStr = read_file_to_string("/sys/devices/system/node/online");
if (!nodeIdsStr.has_value() || nodeIdsStr->empty())
{
fallback();
}
else
{
remove_whitespace(*nodeIdsStr);
for (size_t n : indices_from_shortened_string(*nodeIdsStr))
{
// /sys/devices/system/node/node.../cpulist
std::string path =
std::string("/sys/devices/system/node/node") + std::to_string(n) + "/cpulist";
auto cpuIdsStr = read_file_to_string(path);
// Now, we only bail if the file does not exist. Some nodes may be
// empty, that's fine. An empty node still has a file that appears
// to have some whitespace, so we need to handle that.
if (!cpuIdsStr.has_value())
{
fallback();
break;
}
else
{
remove_whitespace(*cpuIdsStr);
for (size_t c : indices_from_shortened_string(*cpuIdsStr))
{
if (is_cpu_allowed(c))
cfg.add_cpu_to_node(n, c);
}
}
}
}
if (useFallback)
{
for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c)
if (is_cpu_allowed(c))
cfg.add_cpu_to_node(NumaIndex{0}, c);
}
#elif defined(_WIN64)
std::optional<std::set<CpuIndex>> allowedCpus; std::optional<std::set<CpuIndex>> allowedCpus;
@@ -555,28 +565,38 @@ class NumaConfig {
return !allowedCpus.has_value() || allowedCpus->count(c) == 1; return !allowedCpus.has_value() || allowedCpus->count(c) == 1;
}; };
WORD numProcGroups = GetActiveProcessorGroupCount(); #elif defined(__linux__) && !defined(__ANDROID__)
for (WORD procGroup = 0; procGroup < numProcGroups; ++procGroup)
{
for (BYTE number = 0; number < WIN_PROCESSOR_GROUP_SIZE; ++number)
{
PROCESSOR_NUMBER procnum;
procnum.Group = procGroup;
procnum.Number = number;
procnum.Reserved = 0;
USHORT nodeNumber;
const BOOL status = GetNumaProcessorNodeEx(&procnum, &nodeNumber); std::set<CpuIndex> allowedCpus;
const CpuIndex c = static_cast<CpuIndex>(procGroup) * WIN_PROCESSOR_GROUP_SIZE
+ static_cast<CpuIndex>(number); if (respectProcessAffinity)
if (status != 0 && nodeNumber != std::numeric_limits<USHORT>::max() allowedCpus = STARTUP_PROCESSOR_AFFINITY;
&& is_cpu_allowed(c))
{ auto is_cpu_allowed = [respectProcessAffinity, &allowedCpus](CpuIndex c) {
cfg.add_cpu_to_node(nodeNumber, c); return !respectProcessAffinity || allowedCpus.count(c) == 1;
} };
#endif
bool l3Success = false;
if (!std::holds_alternative<SystemNumaPolicy>(policy))
{
size_t l3BundleSize = 0;
if (const auto* v = std::get_if<BundledL3Policy>(&policy))
{
l3BundleSize = v->bundleSize;
}
if (auto l3Cfg =
try_get_l3_aware_config(respectProcessAffinity, l3BundleSize, is_cpu_allowed))
{
cfg = std::move(*l3Cfg);
l3Success = true;
} }
} }
if (!l3Success)
cfg = from_system_numa(respectProcessAffinity, is_cpu_allowed);
#if defined(_WIN64)
// Split the NUMA nodes to be contained within a group if necessary. // Split the NUMA nodes to be contained within a group if necessary.
// This is needed between Windows 10 Build 20348 and Windows 11, because // This is needed between Windows 10 Build 20348 and Windows 11, because
// the new NUMA allocation behaviour was introduced while there was // the new NUMA allocation behaviour was introduced while there was
@@ -623,12 +643,7 @@ class NumaConfig {
cfg = std::move(splitCfg); cfg = std::move(splitCfg);
} }
#endif
#else
// Fallback for unsupported systems.
for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c)
cfg.add_cpu_to_node(NumaIndex{0}, c);
#endif #endif
@@ -1041,6 +1056,227 @@ class NumaConfig {
return indices; return indices;
} }
// This function queries the system for the mapping of processors to NUMA nodes.
// On Linux we read from standardized kernel sysfs, with a fallback to single NUMA
// node. On Windows we utilize GetNumaProcessorNodeEx, which has its quirks, see
// comment for Windows implementation of get_process_affinity.
template<typename Pred>
static NumaConfig from_system_numa([[maybe_unused]] bool respectProcessAffinity,
[[maybe_unused]] Pred&& is_cpu_allowed) {
NumaConfig cfg = empty();
#if defined(__linux__) && !defined(__ANDROID__)
// On Linux things are straightforward, since there's no processor groups and
// any thread can be scheduled on all processors.
// We try to gather this information from the sysfs first
// https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-devices-node
bool useFallback = false;
auto fallback = [&]() {
useFallback = true;
cfg = empty();
};
// /sys/devices/system/node/online contains information about active NUMA nodes
auto nodeIdsStr = read_file_to_string("/sys/devices/system/node/online");
if (!nodeIdsStr.has_value() || nodeIdsStr->empty())
{
fallback();
}
else
{
remove_whitespace(*nodeIdsStr);
for (size_t n : indices_from_shortened_string(*nodeIdsStr))
{
// /sys/devices/system/node/node.../cpulist
std::string path =
std::string("/sys/devices/system/node/node") + std::to_string(n) + "/cpulist";
auto cpuIdsStr = read_file_to_string(path);
// Now, we only bail if the file does not exist. Some nodes may be
// empty, that's fine. An empty node still has a file that appears
// to have some whitespace, so we need to handle that.
if (!cpuIdsStr.has_value())
{
fallback();
break;
}
else
{
remove_whitespace(*cpuIdsStr);
for (size_t c : indices_from_shortened_string(*cpuIdsStr))
{
if (is_cpu_allowed(c))
cfg.add_cpu_to_node(n, c);
}
}
}
}
if (useFallback)
{
for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c)
if (is_cpu_allowed(c))
cfg.add_cpu_to_node(NumaIndex{0}, c);
}
#elif defined(_WIN64)
WORD numProcGroups = GetActiveProcessorGroupCount();
for (WORD procGroup = 0; procGroup < numProcGroups; ++procGroup)
{
for (BYTE number = 0; number < WIN_PROCESSOR_GROUP_SIZE; ++number)
{
PROCESSOR_NUMBER procnum;
procnum.Group = procGroup;
procnum.Number = number;
procnum.Reserved = 0;
USHORT nodeNumber;
const BOOL status = GetNumaProcessorNodeEx(&procnum, &nodeNumber);
const CpuIndex c = static_cast<CpuIndex>(procGroup) * WIN_PROCESSOR_GROUP_SIZE
+ static_cast<CpuIndex>(number);
if (status != 0 && nodeNumber != std::numeric_limits<USHORT>::max()
&& is_cpu_allowed(c))
{
cfg.add_cpu_to_node(nodeNumber, c);
}
}
}
#else
abort(); // should not reach here
#endif
return cfg;
}
template<typename Pred>
static std::optional<NumaConfig> try_get_l3_aware_config(
bool respectProcessAffinity, size_t bundleSize, [[maybe_unused]] Pred&& is_cpu_allowed) {
// Get the normal system configuration so we know to which NUMA node
// each L3 domain belongs.
NumaConfig systemConfig =
NumaConfig::from_system(SystemNumaPolicy{}, respectProcessAffinity);
std::vector<L3Domain> l3Domains;
#if defined(__linux__) && !defined(__ANDROID__)
std::set<CpuIndex> seenCpus;
auto nextUnseenCpu = [&seenCpus]() {
for (CpuIndex i = 0;; ++i)
if (!seenCpus.count(i))
return i;
};
while (true)
{
CpuIndex next = nextUnseenCpu();
auto siblingsStr =
read_file_to_string("/sys/devices/system/cpu/cpu" + std::to_string(next)
+ "/cache/index3/shared_cpu_list");
if (!siblingsStr.has_value() || siblingsStr->empty())
{
break; // we have read all available CPUs
}
L3Domain domain;
for (size_t c : indices_from_shortened_string(*siblingsStr))
{
if (is_cpu_allowed(c))
{
domain.systemNumaIndex = systemConfig.nodeByCpu.at(c);
domain.cpus.insert(c);
}
seenCpus.insert(c);
}
if (!domain.cpus.empty())
{
l3Domains.emplace_back(std::move(domain));
}
}
#elif defined(_WIN64)
DWORD bufSize = 0;
GetLogicalProcessorInformationEx(RelationCache, nullptr, &bufSize);
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
return std::nullopt;
std::vector<char> buffer(bufSize);
auto info = reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX>(buffer.data());
if (!GetLogicalProcessorInformationEx(RelationCache, info, &bufSize))
return std::nullopt;
while (reinterpret_cast<char*>(info) < buffer.data() + bufSize)
{
info = std::launder(info);
if (info->Relationship == RelationCache && info->Cache.Level == 3)
{
L3Domain domain{};
domain.cpus = readCacheMembers(info, is_cpu_allowed);
if (!domain.cpus.empty())
{
domain.systemNumaIndex = systemConfig.nodeByCpu.at(*domain.cpus.begin());
l3Domains.push_back(std::move(domain));
}
}
// Variable length data structure, advance to next
info = reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX>(
reinterpret_cast<char*>(info) + info->Size);
}
#endif
if (!l3Domains.empty())
return {NumaConfig::from_l3_info(std::move(l3Domains), bundleSize)};
return std::nullopt;
}
static NumaConfig from_l3_info(std::vector<L3Domain>&& domains, size_t bundleSize) {
assert(!domains.empty());
std::map<NumaIndex, std::vector<L3Domain>> list;
for (auto& d : domains)
list[d.systemNumaIndex].emplace_back(std::move(d));
NumaConfig cfg = empty();
NumaIndex n = 0;
for (auto& [_, ds] : list)
{
bool changed;
// Scan through pairs and merge them. With roughly equal L3 sizes, should give
// a decent distribution.
do
{
changed = false;
for (size_t j = 0; j + 1 < ds.size(); ++j)
{
if (ds[j].cpus.size() + ds[j + 1].cpus.size() <= bundleSize)
{
changed = true;
ds[j].cpus.merge(ds[j + 1].cpus);
ds.erase(ds.begin() + j + 1);
}
}
// ds.size() has decreased if changed is true, so this loop will terminate
} while (changed);
for (const L3Domain& d : ds)
{
const NumaIndex dn = n++;
for (CpuIndex cpu : d.cpus)
{
cfg.add_cpu_to_node(dn, cpu);
}
}
}
return cfg;
}
}; };
class NumaReplicationContext; class NumaReplicationContext;
@@ -1345,12 +1581,12 @@ class LazyNumaReplicatedSystemWide: public NumaReplicatedBase {
std::size_t get_discriminator(NumaIndex idx) const { std::size_t get_discriminator(NumaIndex idx) const {
const NumaConfig& cfg = get_numa_config(); const NumaConfig& cfg = get_numa_config();
const NumaConfig& cfg_sys = NumaConfig::from_system(false); const NumaConfig& cfg_sys = NumaConfig::from_system(SystemNumaPolicy{}, false);
// as a discriminator, locate the hardware/system numadomain this cpuindex belongs to // as a discriminator, locate the hardware/system numadomain this cpuindex belongs to
CpuIndex cpu = *cfg.nodes[idx].begin(); // get a CpuIndex from NumaIndex CpuIndex cpu = *cfg.nodes[idx].begin(); // get a CpuIndex from NumaIndex
NumaIndex sys_idx = cfg_sys.is_cpu_assigned(cpu) ? cfg_sys.nodeByCpu.at(cpu) : 0; NumaIndex sys_idx = cfg_sys.is_cpu_assigned(cpu) ? cfg_sys.nodeByCpu.at(cpu) : 0;
std::string s = cfg_sys.to_string() + "$" + std::to_string(sys_idx); std::string s = cfg_sys.to_string() + "$" + std::to_string(sys_idx);
return std::hash<std::string>{}(s); return static_cast<std::size_t>(hash_string(s));
} }
void ensure_present(NumaIndex idx) const { void ensure_present(NumaIndex idx) const {
+12 -5
View File
@@ -66,12 +66,15 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
os << "\n +---+---+---+---+---+---+---+---+\n"; os << "\n +---+---+---+---+---+---+---+---+\n";
for (Rank r = RANK_8; r >= RANK_1; --r) for (Rank r = RANK_8;; --r)
{ {
for (File f = FILE_A; f <= FILE_H; ++f) for (File f = FILE_A; f <= FILE_H; ++f)
os << " | " << PieceToChar[pos.piece_on(make_square(f, r))]; os << " | " << PieceToChar[pos.piece_on(make_square(f, r))];
os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n"; os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n";
if (r == RANK_1)
break;
} }
os << " a b c d e f g h\n" os << " a b c d e f g h\n"
@@ -416,7 +419,7 @@ string Position::fen() const {
int emptyCnt; int emptyCnt;
std::ostringstream ss; std::ostringstream ss;
for (Rank r = RANK_8; r >= RANK_1; --r) for (Rank r = RANK_8;; --r)
{ {
for (File f = FILE_A; f <= FILE_H; ++f) for (File f = FILE_A; f <= FILE_H; ++f)
{ {
@@ -430,8 +433,9 @@ string Position::fen() const {
ss << PieceToChar[piece_on(make_square(f, r))]; ss << PieceToChar[piece_on(make_square(f, r))];
} }
if (r > RANK_1) if (r == RANK_1)
ss << '/'; break;
ss << '/';
} }
ss << (sideToMove == WHITE ? " w " : " b "); ss << (sideToMove == WHITE ? " w " : " b ");
@@ -1477,10 +1481,13 @@ void Position::flip() {
string f, token; string f, token;
std::stringstream ss(fen()); std::stringstream ss(fen());
for (Rank r = RANK_8; r >= RANK_1; --r) // Piece placement for (Rank r = RANK_8;; --r) // Piece placement
{ {
std::getline(ss, token, r > RANK_1 ? '/' : ' '); std::getline(ss, token, r > RANK_1 ? '/' : ' ');
f.insert(0, token + (f.empty() ? " " : "/")); f.insert(0, token + (f.empty() ? " " : "/"));
if (r == RANK_1)
break;
} }
ss >> token; // Active color ss >> token; // Active color
+4 -5
View File
@@ -1206,8 +1206,7 @@ moves_loop: // When in check, search starts here
// Increase reduction if next ply has a lot of fail high // Increase reduction if next ply has a lot of fail high
if ((ss + 1)->cutoffCnt > 1) if ((ss + 1)->cutoffCnt > 1)
r += 120 + 1024 * ((ss + 1)->cutoffCnt > 2) + 100 * ((ss + 1)->cutoffCnt > 3) r += 256 + 1024 * ((ss + 1)->cutoffCnt > 2) + 1024 * allNode;
+ 1024 * allNode;
// For first picked move (ttMove) reduce reduction // For first picked move (ttMove) reduce reduction
if (move == ttData.move) if (move == ttData.move)
@@ -1433,6 +1432,7 @@ moves_loop: // When in check, search starts here
bonusScale = std::max(bonusScale, 0); bonusScale = std::max(bonusScale, 0);
// scaledBonus ranges from 0 to roughly 2.3M, overflows happen for multipliers larger than 900
const int scaledBonus = std::min(141 * depth - 87, 1351) * bonusScale; const int scaledBonus = std::min(141 * depth - 87, 1351) * bonusScale;
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq,
@@ -1441,8 +1441,7 @@ moves_loop: // When in check, search starts here
mainHistory[~us][((ss - 1)->currentMove).raw()] << scaledBonus * 243 / 32768; mainHistory[~us][((ss - 1)->currentMove).raw()] << scaledBonus * 243 / 32768;
if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION)
sharedHistory.pawn_entry(pos)[pos.piece_on(prevSq)][prevSq] sharedHistory.pawn_entry(pos)[pos.piece_on(prevSq)][prevSq] << scaledBonus * 290 / 8192;
<< scaledBonus * 1160 / 32768;
} }
// Bonus for prior capture countermove that caused the fail low // Bonus for prior capture countermove that caused the fail low
@@ -1653,7 +1652,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
// we can prune this move. // we can prune this move.
if (!pos.see_ge(move, alpha - futilityBase)) if (!pos.see_ge(move, alpha - futilityBase))
{ {
bestValue = std::min(alpha, futilityBase); bestValue = std::max(bestValue, std::min(alpha, futilityBase));
continue; continue;
} }
} }
+12 -14
View File
@@ -20,6 +20,7 @@
#define SHM_H_INCLUDED #define SHM_H_INCLUDED
#include <algorithm> #include <algorithm>
#include <cinttypes>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
@@ -35,7 +36,7 @@
#include <utility> #include <utility>
#include <variant> #include <variant>
#if !defined(_WIN32) && !defined(__ANDROID__) #if defined(__linux__) && !defined(__ANDROID__)
#include "shm_linux.h" #include "shm_linux.h"
#endif #endif
@@ -59,7 +60,7 @@
#define NOMINMAX #define NOMINMAX
#endif #endif
#include <windows.h> #include <windows.h>
#else #elif defined(__linux__)
#include <cstring> #include <cstring>
#include <fcntl.h> #include <fcntl.h>
#include <pthread.h> #include <pthread.h>
@@ -406,7 +407,7 @@ class SharedMemoryBackend {
std::string last_error_message; std::string last_error_message;
}; };
#elif !defined(__ANDROID__) #elif defined(__linux__) && !defined(__ANDROID__)
template<typename T> template<typename T>
class SharedMemoryBackend { class SharedMemoryBackend {
@@ -512,12 +513,9 @@ template<typename T>
struct SystemWideSharedConstant { struct SystemWideSharedConstant {
private: private:
static std::string createHashString(const std::string& input) { static std::string createHashString(const std::string& input) {
size_t hash = std::hash<std::string>{}(input); char buf[1024];
std::snprintf(buf, sizeof(buf), "%016" PRIx64, hash_string(input));
std::stringstream ss; return buf;
ss << std::hex << std::setfill('0') << hash;
return ss.str();
} }
public: public:
@@ -534,13 +532,13 @@ struct SystemWideSharedConstant {
// that are not present in the content, for example NUMA node allocation. // that are not present in the content, for example NUMA node allocation.
SystemWideSharedConstant(const T& value, std::size_t discriminator = 0) { SystemWideSharedConstant(const T& value, std::size_t discriminator = 0) {
std::size_t content_hash = std::hash<T>{}(value); std::size_t content_hash = std::hash<T>{}(value);
std::size_t executable_hash = std::hash<std::string>{}(getExecutablePathHash()); std::size_t executable_hash = hash_string(getExecutablePathHash());
std::string shm_name = std::string("Local\\sf_") + std::to_string(content_hash) + "$" char buf[1024];
+ std::to_string(executable_hash) + "$" std::snprintf(buf, sizeof(buf), "Local\\sf_%zu$%zu$%zu", content_hash, executable_hash, discriminator);
+ std::to_string(discriminator); std::string shm_name = buf;
#if !defined(_WIN32) #if defined(__linux__) && !defined(__ANDROID__)
// POSIX shared memory names must start with a slash // POSIX shared memory names must start with a slash
shm_name = "/sf_" + createHashString(shm_name); shm_name = "/sf_" + createHashString(shm_name);
+43 -30
View File
@@ -19,6 +19,10 @@
#ifndef SHM_LINUX_H_INCLUDED #ifndef SHM_LINUX_H_INCLUDED
#define SHM_LINUX_H_INCLUDED #define SHM_LINUX_H_INCLUDED
#if !defined(__linux__) || defined(__ANDROID__)
#error shm_linux.h should not be included on this platform.
#endif
#include <atomic> #include <atomic>
#include <cassert> #include <cassert>
#include <cerrno> #include <cerrno>
@@ -33,7 +37,6 @@
#include <string> #include <string>
#include <inttypes.h> #include <inttypes.h>
#include <type_traits> #include <type_traits>
#include <unordered_set>
#include <fcntl.h> #include <fcntl.h>
#include <signal.h> #include <signal.h>
@@ -41,16 +44,10 @@
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include <limits.h>
#define SF_MAX_SEM_NAME_LEN NAME_MAX
#if defined(__NetBSD__) || defined(__DragonFly__) || defined(__linux__) #include "misc.h"
#include <limits.h>
#define SF_MAX_SEM_NAME_LEN NAME_MAX
#elif defined(__APPLE__)
#define SF_MAX_SEM_NAME_LEN 31
#else
#define SF_MAX_SEM_NAME_LEN 255
#endif
namespace Stockfish::shm { namespace Stockfish::shm {
@@ -67,48 +64,60 @@ struct ShmHeader {
class SharedMemoryBase { class SharedMemoryBase {
public: public:
virtual ~SharedMemoryBase() = default; virtual ~SharedMemoryBase() = default;
virtual void close() noexcept = 0; virtual void close(bool skip_unmap = false) noexcept = 0;
virtual const std::string& name() const noexcept = 0; virtual const std::string& name() const noexcept = 0;
}; };
class SharedMemoryRegistry { class SharedMemoryRegistry {
private: private:
static std::mutex registry_mutex_; static std::mutex registry_mutex_;
static std::unordered_set<SharedMemoryBase*> active_instances_; static std::vector<SharedMemoryBase*> active_instances_;
public: public:
static void register_instance(SharedMemoryBase* instance) { static void register_instance(SharedMemoryBase* instance) {
std::scoped_lock lock(registry_mutex_); std::scoped_lock lock(registry_mutex_);
active_instances_.insert(instance); active_instances_.push_back(instance);
} }
static void unregister_instance(SharedMemoryBase* instance) { static void unregister_instance(SharedMemoryBase* instance) {
std::scoped_lock lock(registry_mutex_); std::scoped_lock lock(registry_mutex_);
active_instances_.erase(instance); active_instances_.erase(
std::remove(active_instances_.begin(), active_instances_.end(), instance), active_instances_.end());
} }
static void cleanup_all() noexcept { static void cleanup_all(bool skip_unmap = false) noexcept {
std::scoped_lock lock(registry_mutex_); std::scoped_lock lock(registry_mutex_);
for (auto* instance : active_instances_) for (auto* instance : active_instances_)
instance->close(); instance->close(skip_unmap);
active_instances_.clear(); active_instances_.clear();
} }
}; };
inline std::mutex SharedMemoryRegistry::registry_mutex_; inline std::mutex SharedMemoryRegistry::registry_mutex_;
inline std::unordered_set<SharedMemoryBase*> SharedMemoryRegistry::active_instances_; inline std::vector<SharedMemoryBase*> SharedMemoryRegistry::active_instances_;
class CleanupHooks { class CleanupHooks {
private: private:
static std::once_flag register_once_; static std::once_flag register_once_;
static void handle_signal(int sig) noexcept { static void handle_signal(int sig) noexcept {
SharedMemoryRegistry::cleanup_all(); // Search threads may still be running, so skip munmap (but still perform
_Exit(128 + sig); // other cleanup actions). The memory mappings will be released on exit.
SharedMemoryRegistry::cleanup_all(true);
// Invoke the default handler, which will exit
struct sigaction sa;
sa.sa_handler = SIG_DFL;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(sig, &sa, nullptr) == -1)
_Exit(128 + sig);
raise(sig);
} }
static void register_signal_handlers() noexcept { static void register_signal_handlers() noexcept {
std::atexit([]() { SharedMemoryRegistry::cleanup_all(); }); std::atexit([]() { SharedMemoryRegistry::cleanup_all(true); });
constexpr int signals[] = {SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGFPE, constexpr int signals[] = {SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGFPE,
SIGSEGV, SIGTERM, SIGBUS, SIGSYS, SIGXCPU, SIGXFSZ}; SIGSEGV, SIGTERM, SIGBUS, SIGSYS, SIGXCPU, SIGXFSZ};
@@ -170,9 +179,10 @@ class SharedMemory: public detail::SharedMemoryBase {
} }
static std::string make_sentinel_base(const std::string& name) { static std::string make_sentinel_base(const std::string& name) {
uint64_t hash = std::hash<std::string>{}(name);
char buf[32]; char buf[32];
std::snprintf(buf, sizeof(buf), "sfshm_%016" PRIx64, static_cast<uint64_t>(hash)); // Using std::to_string here causes non-deterministic PGO builds.
// snprintf, being part of libc, is insensitive to the formatted values.
std::snprintf(buf, sizeof(buf), "sfshm_%016" PRIu64, hash_string(name));
return buf; return buf;
} }
@@ -318,7 +328,7 @@ class SharedMemory: public detail::SharedMemoryBase {
} }
} }
void close() noexcept override { void close(bool skip_unmap = false) noexcept override {
if (fd_ == -1 && mapped_ptr_ == nullptr) if (fd_ == -1 && mapped_ptr_ == nullptr)
return; return;
@@ -345,7 +355,10 @@ class SharedMemory: public detail::SharedMemoryBase {
decrement_refcount_relaxed(); decrement_refcount_relaxed();
} }
unmap_region(); if (skip_unmap)
mapped_ptr_ = nullptr;
else
unmap_region();
if (remove_region) if (remove_region)
shm_unlink(name_.c_str()); shm_unlink(name_.c_str());
@@ -359,7 +372,8 @@ class SharedMemory: public detail::SharedMemoryBase {
fd_ = -1; fd_ = -1;
} }
reset(); if (!skip_unmap)
reset();
} }
const std::string& name() const noexcept override { return name_; } const std::string& name() const noexcept override { return name_; }
@@ -427,11 +441,10 @@ class SharedMemory: public detail::SharedMemoryBase {
} }
std::string sentinel_full_path(pid_t pid) const { std::string sentinel_full_path(pid_t pid) const {
std::string path = "/dev/shm/"; char buf[1024];
path += sentinel_base_; // See above snprintf comment
path.push_back('.'); std::snprintf(buf, sizeof(buf), "/dev/shm/%s.%ld", sentinel_base_.c_str(), long(pid));
path += std::to_string(pid); return buf;
return path;
} }
void decrement_refcount_relaxed() noexcept { void decrement_refcount_relaxed() noexcept {
+16 -11
View File
@@ -709,15 +709,11 @@ int map_score(TBTable<DTZ>* entry, File f, int value, WDLScore wdl) {
return value + 1; return value + 1;
} }
// A temporary fix for the compiler bug with AVX-512. (#4450) // A temporary fix for the compiler bug with vectorization. (#4450)
#ifdef USE_AVX512 #if defined(__clang__) && defined(__clang_major__) && __clang_major__ >= 15
#if defined(__clang__) && defined(__clang_major__) && __clang_major__ >= 15 #define DISABLE_CLANG_LOOP_VEC _Pragma("clang loop vectorize(disable)")
#define CLANG_AVX512_BUG_FIX __attribute__((optnone)) #else
#endif #define DISABLE_CLANG_LOOP_VEC
#endif
#ifndef CLANG_AVX512_BUG_FIX
#define CLANG_AVX512_BUG_FIX
#endif #endif
// Compute a unique index out of a position and use it to probe the TB file. To // Compute a unique index out of a position and use it to probe the TB file. To
@@ -727,8 +723,7 @@ int map_score(TBTable<DTZ>* entry, File f, int value, WDLScore wdl) {
// idx = Binomial[1][s1] + Binomial[2][s2] + ... + Binomial[k][sk] // idx = Binomial[1][s1] + Binomial[2][s2] + ... + Binomial[k][sk]
// //
template<typename T, typename Ret = typename T::Ret> template<typename T, typename Ret = typename T::Ret>
CLANG_AVX512_BUG_FIX Ret Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) {
do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) {
Square squares[TBPIECES]; Square squares[TBPIECES];
Piece pieces[TBPIECES]; Piece pieces[TBPIECES];
@@ -812,8 +807,11 @@ do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result)
// Now we map again the squares so that the square of the lead piece is in // Now we map again the squares so that the square of the lead piece is in
// the triangle A1-D1-D4. // the triangle A1-D1-D4.
if (file_of(squares[0]) > FILE_D) if (file_of(squares[0]) > FILE_D)
{
DISABLE_CLANG_LOOP_VEC
for (int i = 0; i < size; ++i) for (int i = 0; i < size; ++i)
squares[i] = flip_file(squares[i]); squares[i] = flip_file(squares[i]);
}
// Encode leading pawns starting with the one with minimum MapPawns[] and // Encode leading pawns starting with the one with minimum MapPawns[] and
// proceeding in ascending order. // proceeding in ascending order.
@@ -832,19 +830,26 @@ do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result)
// In positions without pawns, we further flip the squares to ensure leading // In positions without pawns, we further flip the squares to ensure leading
// piece is below RANK_5. // piece is below RANK_5.
if (rank_of(squares[0]) > RANK_4) if (rank_of(squares[0]) > RANK_4)
{
DISABLE_CLANG_LOOP_VEC
for (int i = 0; i < size; ++i) for (int i = 0; i < size; ++i)
squares[i] = flip_rank(squares[i]); squares[i] = flip_rank(squares[i]);
}
// Look for the first piece of the leading group not on the A1-D4 diagonal // Look for the first piece of the leading group not on the A1-D4 diagonal
// and ensure it is mapped below the diagonal. // and ensure it is mapped below the diagonal.
DISABLE_CLANG_LOOP_VEC
for (int i = 0; i < d->groupLen[0]; ++i) for (int i = 0; i < d->groupLen[0]; ++i)
{ {
if (!off_A1H8(squares[i])) if (!off_A1H8(squares[i]))
continue; continue;
if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C1 if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C1
{
DISABLE_CLANG_LOOP_VEC
for (int j = i; j < size; ++j) for (int j = i; j < size; ++j)
squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63); squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63);
}
break; break;
} }
+23 -14
View File
@@ -213,22 +213,31 @@ void ThreadPool::set(const NumaConfig& numaConfig,
while (threads.size() < requested) while (threads.size() < requested)
{ {
const size_t threadId = threads.size(); const size_t threadId = threads.size();
const NumaIndex numaId = doBindThreads ? boundThreadToNumaNode[threadId] : 0; const NumaIndex numaId = doBindThreads ? boundThreadToNumaNode[threadId] : 0;
auto manager = threadId == 0 ? std::unique_ptr<Search::ISearchManager>( auto create_thread = [&]() {
std::make_unique<Search::SearchManager>(updateContext)) auto manager = threadId == 0
: std::make_unique<Search::NullSearchManager>(); ? std::unique_ptr<Search::ISearchManager>(
std::make_unique<Search::SearchManager>(updateContext))
: std::make_unique<Search::NullSearchManager>();
// When not binding threads we want to force all access to happen // When not binding threads we want to force all access to happen
// from the same NUMA node, because in case of NUMA replicated memory // from the same NUMA node, because in case of NUMA replicated memory
// accesses we don't want to trash cache in case the threads get scheduled // accesses we don't want to trash cache in case the threads get scheduled
// on the same NUMA node. // on the same NUMA node.
auto binder = doBindThreads ? OptionalThreadToNumaNodeBinder(numaConfig, numaId) auto binder = doBindThreads ? OptionalThreadToNumaNodeBinder(numaConfig, numaId)
: OptionalThreadToNumaNodeBinder(numaId); : OptionalThreadToNumaNodeBinder(numaId);
threads.emplace_back(std::make_unique<Thread>(sharedState, std::move(manager), threadId, threads.emplace_back(std::make_unique<Thread>(sharedState, std::move(manager),
counts[numaId]++, threadsPerNode[numaId], threadId, counts[numaId]++,
binder)); threadsPerNode[numaId], binder));
};
// Ensure the worker thread inherits the intended NUMA affinity at creation.
if (doBindThreads)
numaConfig.execute_on_numa_node(numaId, create_thread);
else
create_thread();
} }
clear(); clear();
+1 -1
View File
@@ -26,7 +26,7 @@
namespace Stockfish { namespace Stockfish {
class OptionsMap; class OptionsMap;
enum Color : int8_t; enum Color : uint8_t;
namespace Search { namespace Search {
struct LimitsType; struct LimitsType;
+9 -9
View File
@@ -117,13 +117,13 @@ using Bitboard = uint64_t;
constexpr int MAX_MOVES = 256; constexpr int MAX_MOVES = 256;
constexpr int MAX_PLY = 246; constexpr int MAX_PLY = 246;
enum Color : int8_t { enum Color : uint8_t {
WHITE, WHITE,
BLACK, BLACK,
COLOR_NB = 2 COLOR_NB = 2
}; };
enum CastlingRights : int8_t { enum CastlingRights : uint8_t {
NO_CASTLING, NO_CASTLING,
WHITE_OO, WHITE_OO,
WHITE_OOO = WHITE_OO << 1, WHITE_OOO = WHITE_OO << 1,
@@ -139,7 +139,7 @@ enum CastlingRights : int8_t {
CASTLING_RIGHT_NB = 16 CASTLING_RIGHT_NB = 16
}; };
enum Bound : int8_t { enum Bound : uint8_t {
BOUND_NONE, BOUND_NONE,
BOUND_UPPER, BOUND_UPPER,
BOUND_LOWER, BOUND_LOWER,
@@ -190,13 +190,13 @@ constexpr Value QueenValue = 2538;
// clang-format off // clang-format off
enum PieceType : std::int8_t { enum PieceType : std::uint8_t {
NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING,
ALL_PIECES = 0, ALL_PIECES = 0,
PIECE_TYPE_NB = 8 PIECE_TYPE_NB = 8
}; };
enum Piece : std::int8_t { enum Piece : std::uint8_t {
NO_PIECE, NO_PIECE,
W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,
@@ -227,7 +227,7 @@ constexpr Depth DEPTH_UNSEARCHED = -2;
constexpr Depth DEPTH_ENTRY_OFFSET = -3; constexpr Depth DEPTH_ENTRY_OFFSET = -3;
// clang-format off // clang-format off
enum Square : int8_t { enum Square : uint8_t {
SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1,
SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2,
SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3,
@@ -255,7 +255,7 @@ enum Direction : int8_t {
NORTH_WEST = NORTH + WEST NORTH_WEST = NORTH + WEST
}; };
enum File : int8_t { enum File : uint8_t {
FILE_A, FILE_A,
FILE_B, FILE_B,
FILE_C, FILE_C,
@@ -267,7 +267,7 @@ enum File : int8_t {
FILE_NB FILE_NB
}; };
enum Rank : int8_t { enum Rank : uint8_t {
RANK_1, RANK_1,
RANK_2, RANK_2,
RANK_3, RANK_3,
@@ -406,7 +406,7 @@ constexpr Key make_key(uint64_t seed) {
} }
enum MoveType { enum MoveType : uint16_t {
NORMAL, NORMAL,
PROMOTION = 1 << 14, PROMOTION = 1 << 14,
EN_PASSANT = 2 << 14, EN_PASSANT = 2 << 14,
+2 -2
View File
@@ -507,8 +507,8 @@ WinRateParams win_rate_params(const Position& pos) {
double m = std::clamp(material, 17, 78) / 58.0; double m = std::clamp(material, 17, 78) / 58.0;
// Return a = p_a(material) and b = p_b(material), see github.com/official-stockfish/WDL_model // Return a = p_a(material) and b = p_b(material), see github.com/official-stockfish/WDL_model
constexpr double as[] = {-13.50030198, 40.92780883, -36.82753545, 386.83004070}; constexpr double as[] = {-72.32565836, 185.93832038, -144.58862193, 416.44950446};
constexpr double bs[] = {96.53354896, -165.79058388, 90.89679019, 49.29561889}; constexpr double bs[] = {83.86794042, -136.06112997, 69.98820887, 47.62901433};
double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3];
double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3];
+1 -1
View File
@@ -33,7 +33,7 @@ namespace Stockfish {
class Position; class Position;
class Move; class Move;
class Score; class Score;
enum Square : int8_t; enum Square : uint8_t;
using Value = int; using Value = int;
class UCIEngine { class UCIEngine {