mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-22 12:47:08 +00:00
Compare commits
18
Commits
@@ -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
|
||||
@@ -120,3 +120,5 @@ jobs:
|
||||
contents: write # For deleting/creating a (pre)release
|
||||
secrets:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
CompilerCheck:
|
||||
uses: ./.github/workflows/avx2_compilers.yml
|
||||
|
||||
@@ -111,6 +111,7 @@ Hongzhi Cheng
|
||||
Ivan Ivec (IIvec)
|
||||
Jacques B. (Timshel)
|
||||
Jake Senne (w1wwwwww)
|
||||
Jakub Ciolek (jake-ciolek)
|
||||
Jan Ondruš (hxim)
|
||||
Jared Kish (Kurtbusch, kurt22i)
|
||||
Jarrod Torriero (DU-jdto)
|
||||
|
||||
+4
-1
@@ -49,12 +49,15 @@ std::string Bitboards::pretty(Bitboard b) {
|
||||
|
||||
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)
|
||||
s += b & make_square(f, r) ? "| X " : "| ";
|
||||
|
||||
s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n";
|
||||
|
||||
if (r == RANK_1)
|
||||
break;
|
||||
}
|
||||
s += " a b c d e f g h\n";
|
||||
|
||||
|
||||
+13
-11
@@ -52,19 +52,21 @@ constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq
|
||||
constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
|
||||
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) :
|
||||
binaryDirectory(path ? CommandLine::get_binary_directory(*path) : ""),
|
||||
numaContext(NumaConfig::from_system()),
|
||||
numaContext(NumaConfig::from_system(DefaultNumaPolicy)),
|
||||
states(new std::deque<StateInfo>(1)),
|
||||
threads(),
|
||||
networks(
|
||||
numaContext,
|
||||
// Heap-allocate because sizeof(NN::Networks) is large
|
||||
std::make_unique<NN::Networks>(
|
||||
std::make_unique<NN::NetworkBig>(NN::EvalFile{EvalFileDefaultNameBig, "None", ""},
|
||||
NN::EmbeddedNNUEType::BIG),
|
||||
std::make_unique<NN::NetworkSmall>(NN::EvalFile{EvalFileDefaultNameSmall, "None", ""},
|
||||
NN::EmbeddedNNUEType::SMALL))) {
|
||||
networks(numaContext,
|
||||
// Heap-allocate because sizeof(NN::Networks) is large
|
||||
std::make_unique<NN::Networks>(NN::EvalFile{EvalFileDefaultNameBig, "None", ""},
|
||||
NN::EvalFile{EvalFileDefaultNameSmall, "None", ""})) {
|
||||
|
||||
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) {
|
||||
if (o == "auto" || o == "system")
|
||||
{
|
||||
numaContext.set_numa_config(NumaConfig::from_system());
|
||||
numaContext.set_numa_config(NumaConfig::from_system(DefaultNumaPolicy));
|
||||
}
|
||||
else if (o == "hardware")
|
||||
{
|
||||
// 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")
|
||||
{
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ namespace Stockfish {
|
||||
namespace {
|
||||
|
||||
// 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
|
||||
// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We
|
||||
|
||||
+28
-13
@@ -34,6 +34,7 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#define stringify2(x) #x
|
||||
@@ -305,24 +306,38 @@ inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
inline void hash_combine(std::size_t& seed, const T& v) {
|
||||
std::hash<T> hasher;
|
||||
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void hash_combine(std::size_t& seed, const std::size_t& v) {
|
||||
seed ^= v + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
||||
inline std::uint64_t hash_bytes(const char* data, std::size_t size) {
|
||||
// FNV-1a 64-bit
|
||||
const char* p = data;
|
||||
std::uint64_t h = 14695981039346656037ull;
|
||||
for (std::size_t i = 0; i < size; ++i)
|
||||
h = (h ^ p[i]) * 1099511628211ull;
|
||||
return h;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline std::size_t get_raw_data_hash(const T& value) {
|
||||
return std::hash<std::string_view>{}(
|
||||
std::string_view(reinterpret_cast<const char*>(&value), sizeof(value)));
|
||||
// We must have no padding bytes because we're reinterpreting as char
|
||||
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>
|
||||
class FixedString {
|
||||
public:
|
||||
@@ -450,7 +465,7 @@ void move_to_front(std::vector<T>& vec, Predicate pred) {
|
||||
template<std::size_t N>
|
||||
struct std::hash<Stockfish::FixedString<N>> {
|
||||
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
@@ -47,10 +47,10 @@ template<Direction offset>
|
||||
inline Move* splat_pawn_moves(Move* moveList, Bitboard to_bb) {
|
||||
alignas(64) static constexpr auto SPLAT_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)};
|
||||
table[i] = {Move(from, Square{i})};
|
||||
Square from{uint8_t(std::clamp(i - offset, 0, 63))};
|
||||
table[i] = {Move(from, Square{uint8_t(i)})};
|
||||
}
|
||||
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) {
|
||||
alignas(64) static constexpr auto SPLAT_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})};
|
||||
return table;
|
||||
}();
|
||||
|
||||
@@ -37,26 +37,6 @@ struct HelperOffsets {
|
||||
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 = {
|
||||
W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_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 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)
|
||||
{
|
||||
@@ -189,8 +169,10 @@ constexpr auto init_index_luts() {
|
||||
+ (color_of(attacked) * (numValidTargets[attacker] / 2) + map)
|
||||
* helper_offsets[attacker].cumulativePieceOffset;
|
||||
|
||||
bool excluded = map < 0;
|
||||
indices[attacker][attacked] = PiecePairData(excluded, semi_excluded, feature);
|
||||
bool excluded = map < 0;
|
||||
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
|
||||
// as offsets[attacker][from]
|
||||
|
||||
// [attacker][attacked]
|
||||
// [attacker][attacked][from < to]
|
||||
constexpr auto index_lut1 = init_index_luts();
|
||||
// [attacker][from][to]
|
||||
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 attacked_oriented = attacked ^ swap;
|
||||
|
||||
const auto piecePairData = index_lut1[attacker_oriented][attacked_oriented];
|
||||
|
||||
const bool less_than = 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;
|
||||
return index_lut1[attacker_oriented][attacked_oriented][from_oriented < to_oriented]
|
||||
+ offsets[attacker_oriented][from_oriented]
|
||||
+ index_lut2[attacker_oriented][from_oriented][to_oriented];
|
||||
}
|
||||
|
||||
// Get a list of indices for active features in ascending order
|
||||
|
||||
+3
-4
@@ -28,7 +28,6 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include "../misc.h"
|
||||
#include "../types.h"
|
||||
@@ -130,9 +129,9 @@ using NetworkSmall = Network<SmallNetworkArchitecture, SmallFeatureTransformer>;
|
||||
|
||||
|
||||
struct Networks {
|
||||
Networks(std::unique_ptr<NetworkBig>&& nB, std::unique_ptr<NetworkSmall>&& nS) :
|
||||
big(std::move(*nB)),
|
||||
small(std::move(*nS)) {}
|
||||
Networks(EvalFile bigFile, EvalFile smallFile) :
|
||||
big(bigFile, EmbeddedNNUEType::BIG),
|
||||
small(smallFile, EmbeddedNNUEType::SMALL) {}
|
||||
|
||||
NetworkBig big;
|
||||
NetworkSmall small;
|
||||
|
||||
+40
-36
@@ -169,52 +169,56 @@ inline void write_little_endian(std::ostream& stream, const IntType* values, std
|
||||
write_little_endian<IntType>(stream, values[i]);
|
||||
}
|
||||
|
||||
|
||||
// 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.
|
||||
// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme.
|
||||
template<typename IntType, std::size_t Count>
|
||||
inline void read_leb_128(std::istream& stream, std::array<IntType, Count>& out) {
|
||||
template<typename BufType, typename IntType, std::size_t Count>
|
||||
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
|
||||
char leb128MagicString[Leb128MagicStringSize];
|
||||
stream.read(leb128MagicString, Leb128MagicStringSize);
|
||||
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;
|
||||
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);
|
||||
}
|
||||
(read_leb_128_detail(stream, outs, bytes_left, buf, buf_pos), ...);
|
||||
|
||||
assert(bytes_left == 0);
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ class FeatureTransformer {
|
||||
permute<16>(biases, PackusEpi16Order);
|
||||
permute<16>(weights, PackusEpi16Order);
|
||||
|
||||
if (UseThreats)
|
||||
if constexpr (UseThreats)
|
||||
permute<8>(threatWeights, PackusEpi16Order);
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ class FeatureTransformer {
|
||||
permute<16>(biases, InversePackusEpi16Order);
|
||||
permute<16>(weights, InversePackusEpi16Order);
|
||||
|
||||
if (UseThreats)
|
||||
if constexpr (UseThreats)
|
||||
permute<8>(threatWeights, InversePackusEpi16Order);
|
||||
}
|
||||
|
||||
@@ -152,40 +152,26 @@ class FeatureTransformer {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
read_leb_128<BiasType>(stream, biases);
|
||||
read_leb_128(stream, biases);
|
||||
|
||||
if (UseThreats)
|
||||
if constexpr (UseThreats)
|
||||
{
|
||||
read_little_endian<ThreatWeightType>(stream, threatWeights.data(),
|
||||
ThreatInputDimensions * HalfDimensions);
|
||||
read_leb_128<WeightType>(stream, weights);
|
||||
read_leb_128(stream, weights);
|
||||
|
||||
auto combinedPsqtWeights =
|
||||
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));
|
||||
read_leb_128(stream, threatPsqtWeights, psqtWeights);
|
||||
}
|
||||
else
|
||||
{
|
||||
read_leb_128<WeightType>(stream, weights);
|
||||
read_leb_128<PSQTWeightType>(stream, psqtWeights);
|
||||
read_leb_128(stream, weights);
|
||||
read_leb_128(stream, psqtWeights);
|
||||
}
|
||||
|
||||
permute_weights();
|
||||
|
||||
if (!UseThreats)
|
||||
if constexpr (!UseThreats)
|
||||
scale_weights(true);
|
||||
|
||||
return !stream.fail();
|
||||
@@ -197,12 +183,12 @@ class FeatureTransformer {
|
||||
|
||||
copy->unpermute_weights();
|
||||
|
||||
if (!UseThreats)
|
||||
if constexpr (!UseThreats)
|
||||
copy->scale_weights(false);
|
||||
|
||||
write_leb_128<BiasType>(stream, copy->biases);
|
||||
|
||||
if (UseThreats)
|
||||
if constexpr (UseThreats)
|
||||
{
|
||||
write_little_endian<ThreatWeightType>(stream, copy->threatWeights.data(),
|
||||
ThreatInputDimensions * HalfDimensions);
|
||||
@@ -256,7 +242,7 @@ class FeatureTransformer {
|
||||
auto psqt =
|
||||
(psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]);
|
||||
|
||||
if (UseThreats)
|
||||
if constexpr (UseThreats)
|
||||
{
|
||||
const auto& threatPsqtAccumulation =
|
||||
(threatAccumulatorState.acc<HalfDimensions>()).psqtAccumulation;
|
||||
@@ -348,7 +334,7 @@ class FeatureTransformer {
|
||||
#else
|
||||
6;
|
||||
#endif
|
||||
if (UseThreats)
|
||||
if constexpr (UseThreats)
|
||||
{
|
||||
const vec_t* tin0 =
|
||||
reinterpret_cast<const vec_t*>(&(threatAccumulation[perspectives[p]][0]));
|
||||
@@ -400,7 +386,7 @@ class FeatureTransformer {
|
||||
BiasType sum1 =
|
||||
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 sum1t =
|
||||
|
||||
+334
-98
@@ -371,6 +371,50 @@ inline WindowsAffinity get_process_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
|
||||
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
@@ -447,14 +491,39 @@ class NumaReplicatedAccessToken {
|
||||
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
|
||||
// existing config in a way that doesn't require recreating it completely, and
|
||||
// it would be complex and expensive to maintain class invariants.
|
||||
// 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
|
||||
// numbering of the NUMA nodes. In particular, empty nodes may be removed, or
|
||||
// the user may create custom nodes. It is guaranteed that NUMA nodes are NOT
|
||||
// empty: every node exposed by NumaConfig has at least one processor assigned.
|
||||
// numbering of the NUMA nodes. In particular, by default, if the processor has
|
||||
// non-uniform cache access within a NUMA node (i.e., a non-unified L3 cache structure),
|
||||
// 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.
|
||||
//
|
||||
@@ -469,78 +538,19 @@ class NumaConfig {
|
||||
add_cpu_range_to_node(NumaIndex{0}, CpuIndex{0}, numCpus - 1);
|
||||
}
|
||||
|
||||
// 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.
|
||||
static NumaConfig from_system([[maybe_unused]] bool respectProcessAffinity = true) {
|
||||
// This function gets a NumaConfig based on the system's provided information.
|
||||
// The available policies are documented above.
|
||||
static NumaConfig from_system([[maybe_unused]] const NumaAutoPolicy& policy,
|
||||
bool respectProcessAffinity = true) {
|
||||
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 (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)
|
||||
#if defined(_WIN64)
|
||||
|
||||
std::optional<std::set<CpuIndex>> allowedCpus;
|
||||
|
||||
@@ -555,28 +565,38 @@ class NumaConfig {
|
||||
return !allowedCpus.has_value() || allowedCpus->count(c) == 1;
|
||||
};
|
||||
|
||||
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;
|
||||
#elif defined(__linux__) && !defined(__ANDROID__)
|
||||
|
||||
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);
|
||||
}
|
||||
std::set<CpuIndex> allowedCpus;
|
||||
|
||||
if (respectProcessAffinity)
|
||||
allowedCpus = STARTUP_PROCESSOR_AFFINITY;
|
||||
|
||||
auto is_cpu_allowed = [respectProcessAffinity, &allowedCpus](CpuIndex 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.
|
||||
// This is needed between Windows 10 Build 20348 and Windows 11, because
|
||||
// the new NUMA allocation behaviour was introduced while there was
|
||||
@@ -623,12 +643,7 @@ class NumaConfig {
|
||||
|
||||
cfg = std::move(splitCfg);
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -1345,12 +1581,12 @@ class LazyNumaReplicatedSystemWide: public NumaReplicatedBase {
|
||||
|
||||
std::size_t get_discriminator(NumaIndex idx) const {
|
||||
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
|
||||
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;
|
||||
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 {
|
||||
|
||||
+12
-5
@@ -66,12 +66,15 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
|
||||
|
||||
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)
|
||||
os << " | " << PieceToChar[pos.piece_on(make_square(f, r))];
|
||||
|
||||
os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n";
|
||||
|
||||
if (r == RANK_1)
|
||||
break;
|
||||
}
|
||||
|
||||
os << " a b c d e f g h\n"
|
||||
@@ -416,7 +419,7 @@ string Position::fen() const {
|
||||
int emptyCnt;
|
||||
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)
|
||||
{
|
||||
@@ -430,8 +433,9 @@ string Position::fen() const {
|
||||
ss << PieceToChar[piece_on(make_square(f, r))];
|
||||
}
|
||||
|
||||
if (r > RANK_1)
|
||||
ss << '/';
|
||||
if (r == RANK_1)
|
||||
break;
|
||||
ss << '/';
|
||||
}
|
||||
|
||||
ss << (sideToMove == WHITE ? " w " : " b ");
|
||||
@@ -1477,10 +1481,13 @@ void Position::flip() {
|
||||
string f, token;
|
||||
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 ? '/' : ' ');
|
||||
f.insert(0, token + (f.empty() ? " " : "/"));
|
||||
|
||||
if (r == RANK_1)
|
||||
break;
|
||||
}
|
||||
|
||||
ss >> token; // Active color
|
||||
|
||||
+4
-5
@@ -1206,8 +1206,7 @@ moves_loop: // When in check, search starts here
|
||||
|
||||
// Increase reduction if next ply has a lot of fail high
|
||||
if ((ss + 1)->cutoffCnt > 1)
|
||||
r += 120 + 1024 * ((ss + 1)->cutoffCnt > 2) + 100 * ((ss + 1)->cutoffCnt > 3)
|
||||
+ 1024 * allNode;
|
||||
r += 256 + 1024 * ((ss + 1)->cutoffCnt > 2) + 1024 * allNode;
|
||||
|
||||
// For first picked move (ttMove) reduce reduction
|
||||
if (move == ttData.move)
|
||||
@@ -1433,6 +1432,7 @@ moves_loop: // When in check, search starts here
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION)
|
||||
sharedHistory.pawn_entry(pos)[pos.piece_on(prevSq)][prevSq]
|
||||
<< scaledBonus * 1160 / 32768;
|
||||
sharedHistory.pawn_entry(pos)[pos.piece_on(prevSq)][prevSq] << scaledBonus * 290 / 8192;
|
||||
}
|
||||
|
||||
// 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.
|
||||
if (!pos.see_ge(move, alpha - futilityBase))
|
||||
{
|
||||
bestValue = std::min(alpha, futilityBase);
|
||||
bestValue = std::max(bestValue, std::min(alpha, futilityBase));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#define SHM_H_INCLUDED
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
@@ -35,7 +36,7 @@
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#if !defined(_WIN32) && !defined(__ANDROID__)
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
#include "shm_linux.h"
|
||||
#endif
|
||||
|
||||
@@ -59,7 +60,7 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#elif defined(__linux__)
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
@@ -406,7 +407,7 @@ class SharedMemoryBackend {
|
||||
std::string last_error_message;
|
||||
};
|
||||
|
||||
#elif !defined(__ANDROID__)
|
||||
#elif defined(__linux__) && !defined(__ANDROID__)
|
||||
|
||||
template<typename T>
|
||||
class SharedMemoryBackend {
|
||||
@@ -512,12 +513,9 @@ template<typename T>
|
||||
struct SystemWideSharedConstant {
|
||||
private:
|
||||
static std::string createHashString(const std::string& input) {
|
||||
size_t hash = std::hash<std::string>{}(input);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::hex << std::setfill('0') << hash;
|
||||
|
||||
return ss.str();
|
||||
char buf[1024];
|
||||
std::snprintf(buf, sizeof(buf), "%016" PRIx64, hash_string(input));
|
||||
return buf;
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -534,13 +532,13 @@ struct SystemWideSharedConstant {
|
||||
// that are not present in the content, for example NUMA node allocation.
|
||||
SystemWideSharedConstant(const T& value, std::size_t discriminator = 0) {
|
||||
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) + "$"
|
||||
+ std::to_string(executable_hash) + "$"
|
||||
+ std::to_string(discriminator);
|
||||
char buf[1024];
|
||||
std::snprintf(buf, sizeof(buf), "Local\\sf_%zu$%zu$%zu", content_hash, executable_hash, discriminator);
|
||||
std::string shm_name = buf;
|
||||
|
||||
#if !defined(_WIN32)
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
// POSIX shared memory names must start with a slash
|
||||
shm_name = "/sf_" + createHashString(shm_name);
|
||||
|
||||
|
||||
+43
-30
@@ -19,6 +19,10 @@
|
||||
#ifndef 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 <cassert>
|
||||
#include <cerrno>
|
||||
@@ -33,7 +37,6 @@
|
||||
#include <string>
|
||||
#include <inttypes.h>
|
||||
#include <type_traits>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
@@ -41,16 +44,10 @@
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
#define SF_MAX_SEM_NAME_LEN NAME_MAX
|
||||
|
||||
#if defined(__NetBSD__) || defined(__DragonFly__) || defined(__linux__)
|
||||
#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
|
||||
|
||||
#include "misc.h"
|
||||
|
||||
namespace Stockfish::shm {
|
||||
|
||||
@@ -67,48 +64,60 @@ struct ShmHeader {
|
||||
class SharedMemoryBase {
|
||||
public:
|
||||
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;
|
||||
};
|
||||
|
||||
class SharedMemoryRegistry {
|
||||
private:
|
||||
static std::mutex registry_mutex_;
|
||||
static std::unordered_set<SharedMemoryBase*> active_instances_;
|
||||
static std::vector<SharedMemoryBase*> active_instances_;
|
||||
|
||||
public:
|
||||
static void register_instance(SharedMemoryBase* instance) {
|
||||
std::scoped_lock lock(registry_mutex_);
|
||||
active_instances_.insert(instance);
|
||||
active_instances_.push_back(instance);
|
||||
}
|
||||
|
||||
static void unregister_instance(SharedMemoryBase* instance) {
|
||||
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_);
|
||||
for (auto* instance : active_instances_)
|
||||
instance->close();
|
||||
instance->close(skip_unmap);
|
||||
active_instances_.clear();
|
||||
}
|
||||
};
|
||||
|
||||
inline std::mutex SharedMemoryRegistry::registry_mutex_;
|
||||
inline std::unordered_set<SharedMemoryBase*> SharedMemoryRegistry::active_instances_;
|
||||
inline std::vector<SharedMemoryBase*> SharedMemoryRegistry::active_instances_;
|
||||
|
||||
class CleanupHooks {
|
||||
private:
|
||||
static std::once_flag register_once_;
|
||||
|
||||
static void handle_signal(int sig) noexcept {
|
||||
SharedMemoryRegistry::cleanup_all();
|
||||
_Exit(128 + sig);
|
||||
// Search threads may still be running, so skip munmap (but still perform
|
||||
// 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 {
|
||||
std::atexit([]() { SharedMemoryRegistry::cleanup_all(); });
|
||||
std::atexit([]() { SharedMemoryRegistry::cleanup_all(true); });
|
||||
|
||||
constexpr int signals[] = {SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGFPE,
|
||||
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) {
|
||||
uint64_t hash = std::hash<std::string>{}(name);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
return;
|
||||
|
||||
@@ -345,7 +355,10 @@ class SharedMemory: public detail::SharedMemoryBase {
|
||||
decrement_refcount_relaxed();
|
||||
}
|
||||
|
||||
unmap_region();
|
||||
if (skip_unmap)
|
||||
mapped_ptr_ = nullptr;
|
||||
else
|
||||
unmap_region();
|
||||
|
||||
if (remove_region)
|
||||
shm_unlink(name_.c_str());
|
||||
@@ -359,7 +372,8 @@ class SharedMemory: public detail::SharedMemoryBase {
|
||||
fd_ = -1;
|
||||
}
|
||||
|
||||
reset();
|
||||
if (!skip_unmap)
|
||||
reset();
|
||||
}
|
||||
|
||||
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 path = "/dev/shm/";
|
||||
path += sentinel_base_;
|
||||
path.push_back('.');
|
||||
path += std::to_string(pid);
|
||||
return path;
|
||||
char buf[1024];
|
||||
// See above snprintf comment
|
||||
std::snprintf(buf, sizeof(buf), "/dev/shm/%s.%ld", sentinel_base_.c_str(), long(pid));
|
||||
return buf;
|
||||
}
|
||||
|
||||
void decrement_refcount_relaxed() noexcept {
|
||||
|
||||
+16
-11
@@ -709,15 +709,11 @@ int map_score(TBTable<DTZ>* entry, File f, int value, WDLScore wdl) {
|
||||
return value + 1;
|
||||
}
|
||||
|
||||
// A temporary fix for the compiler bug with AVX-512. (#4450)
|
||||
#ifdef USE_AVX512
|
||||
#if defined(__clang__) && defined(__clang_major__) && __clang_major__ >= 15
|
||||
#define CLANG_AVX512_BUG_FIX __attribute__((optnone))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef CLANG_AVX512_BUG_FIX
|
||||
#define CLANG_AVX512_BUG_FIX
|
||||
// A temporary fix for the compiler bug with vectorization. (#4450)
|
||||
#if defined(__clang__) && defined(__clang_major__) && __clang_major__ >= 15
|
||||
#define DISABLE_CLANG_LOOP_VEC _Pragma("clang loop vectorize(disable)")
|
||||
#else
|
||||
#define DISABLE_CLANG_LOOP_VEC
|
||||
#endif
|
||||
|
||||
// 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]
|
||||
//
|
||||
template<typename T, typename Ret = typename T::Ret>
|
||||
CLANG_AVX512_BUG_FIX Ret
|
||||
do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) {
|
||||
Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) {
|
||||
|
||||
Square squares[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
|
||||
// the triangle A1-D1-D4.
|
||||
if (file_of(squares[0]) > FILE_D)
|
||||
{
|
||||
DISABLE_CLANG_LOOP_VEC
|
||||
for (int i = 0; i < size; ++i)
|
||||
squares[i] = flip_file(squares[i]);
|
||||
}
|
||||
|
||||
// Encode leading pawns starting with the one with minimum MapPawns[] and
|
||||
// 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
|
||||
// piece is below RANK_5.
|
||||
if (rank_of(squares[0]) > RANK_4)
|
||||
{
|
||||
DISABLE_CLANG_LOOP_VEC
|
||||
for (int i = 0; i < size; ++i)
|
||||
squares[i] = flip_rank(squares[i]);
|
||||
}
|
||||
|
||||
// Look for the first piece of the leading group not on the A1-D4 diagonal
|
||||
// and ensure it is mapped below the diagonal.
|
||||
DISABLE_CLANG_LOOP_VEC
|
||||
for (int i = 0; i < d->groupLen[0]; ++i)
|
||||
{
|
||||
if (!off_A1H8(squares[i]))
|
||||
continue;
|
||||
|
||||
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)
|
||||
squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
+23
-14
@@ -213,22 +213,31 @@ void ThreadPool::set(const NumaConfig& numaConfig,
|
||||
|
||||
while (threads.size() < requested)
|
||||
{
|
||||
const size_t threadId = threads.size();
|
||||
const NumaIndex numaId = doBindThreads ? boundThreadToNumaNode[threadId] : 0;
|
||||
auto manager = threadId == 0 ? std::unique_ptr<Search::ISearchManager>(
|
||||
std::make_unique<Search::SearchManager>(updateContext))
|
||||
: std::make_unique<Search::NullSearchManager>();
|
||||
const size_t threadId = threads.size();
|
||||
const NumaIndex numaId = doBindThreads ? boundThreadToNumaNode[threadId] : 0;
|
||||
auto create_thread = [&]() {
|
||||
auto manager = threadId == 0
|
||||
? 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
|
||||
// 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
|
||||
// on the same NUMA node.
|
||||
auto binder = doBindThreads ? OptionalThreadToNumaNodeBinder(numaConfig, numaId)
|
||||
: OptionalThreadToNumaNodeBinder(numaId);
|
||||
// When not binding threads we want to force all access to happen
|
||||
// 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
|
||||
// on the same NUMA node.
|
||||
auto binder = doBindThreads ? OptionalThreadToNumaNodeBinder(numaConfig, numaId)
|
||||
: OptionalThreadToNumaNodeBinder(numaId);
|
||||
|
||||
threads.emplace_back(std::make_unique<Thread>(sharedState, std::move(manager), threadId,
|
||||
counts[numaId]++, threadsPerNode[numaId],
|
||||
binder));
|
||||
threads.emplace_back(std::make_unique<Thread>(sharedState, std::move(manager),
|
||||
threadId, counts[numaId]++,
|
||||
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();
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
namespace Stockfish {
|
||||
|
||||
class OptionsMap;
|
||||
enum Color : int8_t;
|
||||
enum Color : uint8_t;
|
||||
|
||||
namespace Search {
|
||||
struct LimitsType;
|
||||
|
||||
+9
-9
@@ -117,13 +117,13 @@ using Bitboard = uint64_t;
|
||||
constexpr int MAX_MOVES = 256;
|
||||
constexpr int MAX_PLY = 246;
|
||||
|
||||
enum Color : int8_t {
|
||||
enum Color : uint8_t {
|
||||
WHITE,
|
||||
BLACK,
|
||||
COLOR_NB = 2
|
||||
};
|
||||
|
||||
enum CastlingRights : int8_t {
|
||||
enum CastlingRights : uint8_t {
|
||||
NO_CASTLING,
|
||||
WHITE_OO,
|
||||
WHITE_OOO = WHITE_OO << 1,
|
||||
@@ -139,7 +139,7 @@ enum CastlingRights : int8_t {
|
||||
CASTLING_RIGHT_NB = 16
|
||||
};
|
||||
|
||||
enum Bound : int8_t {
|
||||
enum Bound : uint8_t {
|
||||
BOUND_NONE,
|
||||
BOUND_UPPER,
|
||||
BOUND_LOWER,
|
||||
@@ -190,13 +190,13 @@ constexpr Value QueenValue = 2538;
|
||||
|
||||
|
||||
// clang-format off
|
||||
enum PieceType : std::int8_t {
|
||||
enum PieceType : std::uint8_t {
|
||||
NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING,
|
||||
ALL_PIECES = 0,
|
||||
PIECE_TYPE_NB = 8
|
||||
};
|
||||
|
||||
enum Piece : std::int8_t {
|
||||
enum Piece : std::uint8_t {
|
||||
NO_PIECE,
|
||||
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,
|
||||
@@ -227,7 +227,7 @@ constexpr Depth DEPTH_UNSEARCHED = -2;
|
||||
constexpr Depth DEPTH_ENTRY_OFFSET = -3;
|
||||
|
||||
// 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_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,
|
||||
@@ -255,7 +255,7 @@ enum Direction : int8_t {
|
||||
NORTH_WEST = NORTH + WEST
|
||||
};
|
||||
|
||||
enum File : int8_t {
|
||||
enum File : uint8_t {
|
||||
FILE_A,
|
||||
FILE_B,
|
||||
FILE_C,
|
||||
@@ -267,7 +267,7 @@ enum File : int8_t {
|
||||
FILE_NB
|
||||
};
|
||||
|
||||
enum Rank : int8_t {
|
||||
enum Rank : uint8_t {
|
||||
RANK_1,
|
||||
RANK_2,
|
||||
RANK_3,
|
||||
@@ -406,7 +406,7 @@ constexpr Key make_key(uint64_t seed) {
|
||||
}
|
||||
|
||||
|
||||
enum MoveType {
|
||||
enum MoveType : uint16_t {
|
||||
NORMAL,
|
||||
PROMOTION = 1 << 14,
|
||||
EN_PASSANT = 2 << 14,
|
||||
|
||||
+2
-2
@@ -507,8 +507,8 @@ WinRateParams win_rate_params(const Position& pos) {
|
||||
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
|
||||
constexpr double as[] = {-13.50030198, 40.92780883, -36.82753545, 386.83004070};
|
||||
constexpr double bs[] = {96.53354896, -165.79058388, 90.89679019, 49.29561889};
|
||||
constexpr double as[] = {-72.32565836, 185.93832038, -144.58862193, 416.44950446};
|
||||
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 b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3];
|
||||
|
||||
Reference in New Issue
Block a user