mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-22 12:47:08 +00:00
Merge commit 'a2a7edf4c8fa145667135bf1bc7f4f67016f7608' into cluster
This is the last commit before there are more conflicts.
This commit is contained in:
@@ -68,6 +68,7 @@ Douglas Matos Gomes (dsmsgms)
|
||||
Dubslow
|
||||
Eduardo Cáceres (eduherminio)
|
||||
Eelco de Groot (KingDefender)
|
||||
Ehsan Rashid (erashid)
|
||||
Elvin Liu (solarlight2)
|
||||
erbsenzaehler
|
||||
Ernesto Gatti
|
||||
|
||||
+7
-8
@@ -46,7 +46,8 @@ int Eval::simple_eval(const Position& pos, Color c) {
|
||||
|
||||
bool Eval::use_smallnet(const Position& pos) {
|
||||
int simpleEval = simple_eval(pos, pos.side_to_move());
|
||||
return std::abs(simpleEval) > 992 + 6 * pos.count<PAWN>();
|
||||
int pawnCount = pos.count<PAWN>();
|
||||
return std::abs(simpleEval) > 992 + 6 * pawnCount * pawnCount / 16;
|
||||
}
|
||||
|
||||
// Evaluate is the evaluator for the outer world. It returns a static evaluation
|
||||
@@ -67,7 +68,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks,
|
||||
: networks.big.evaluate(pos, &caches.big, true, &nnueComplexity);
|
||||
|
||||
// Re-evaluate the position when higher eval accuracy is worth the time spent
|
||||
if (smallNet && nnue * simpleEval < 0)
|
||||
if (smallNet && (nnue * simpleEval < 0 || std::abs(nnue) < 250))
|
||||
{
|
||||
nnue = networks.big.evaluate(pos, &caches.big, true, &nnueComplexity);
|
||||
smallNet = false;
|
||||
@@ -75,17 +76,15 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks,
|
||||
|
||||
// Blend optimism and eval with nnue complexity
|
||||
optimism += optimism * nnueComplexity / 470;
|
||||
nnue -= nnue * (nnueComplexity * 5 / 3) / 32621;
|
||||
nnue -= nnue * nnueComplexity / 20000;
|
||||
|
||||
int material = 200 * pos.count<PAWN>() + 350 * pos.count<KNIGHT>() + 400 * pos.count<BISHOP>()
|
||||
int material = 300 * pos.count<PAWN>() + 350 * pos.count<KNIGHT>() + 400 * pos.count<BISHOP>()
|
||||
+ 640 * pos.count<ROOK>() + 1200 * pos.count<QUEEN>();
|
||||
|
||||
v = (nnue * (34000 + material + 135 * pos.count<PAWN>())
|
||||
+ optimism * (4400 + material + 99 * pos.count<PAWN>()))
|
||||
/ 35967;
|
||||
v = (nnue * (34300 + material) + optimism * (4400 + material)) / 36672;
|
||||
|
||||
// Damp down the evaluation linearly when shuffling
|
||||
v = v * (204 - pos.rule50_count()) / 208;
|
||||
v -= v * pos.rule50_count() / 212;
|
||||
|
||||
// Guarantee evaluation does not hit the tablebase range
|
||||
v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
|
||||
|
||||
+2
-2
@@ -33,8 +33,8 @@ namespace Eval {
|
||||
// for the build process (profile-build and fishtest) to work. Do not change the
|
||||
// name of the macro or the location where this macro is defined, as it is used
|
||||
// in the Makefile/Fishtest.
|
||||
#define EvalFileDefaultNameBig "nn-c721dfca8cd3.nnue"
|
||||
#define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue"
|
||||
#define EvalFileDefaultNameBig "nn-ddcfb9224cdb.nnue"
|
||||
#define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue"
|
||||
|
||||
namespace NNUE {
|
||||
struct Networks;
|
||||
|
||||
+34
-30
@@ -34,30 +34,25 @@
|
||||
// the calls at compile time), try to load them at runtime. To do this we need
|
||||
// first to define the corresponding function pointers.
|
||||
extern "C" {
|
||||
using fun1_t = bool (*)(LOGICAL_PROCESSOR_RELATIONSHIP,
|
||||
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX,
|
||||
PDWORD);
|
||||
using fun2_t = bool (*)(USHORT, PGROUP_AFFINITY);
|
||||
using fun3_t = bool (*)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY);
|
||||
using fun4_t = bool (*)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT);
|
||||
using fun5_t = WORD (*)();
|
||||
using fun6_t = bool (*)(HANDLE, DWORD, PHANDLE);
|
||||
using fun7_t = bool (*)(LPCSTR, LPCSTR, PLUID);
|
||||
using fun8_t = bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD);
|
||||
using OpenProcessToken_t = bool (*)(HANDLE, DWORD, PHANDLE);
|
||||
using LookupPrivilegeValueA_t = bool (*)(LPCSTR, LPCSTR, PLUID);
|
||||
using AdjustTokenPrivileges_t =
|
||||
bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD);
|
||||
}
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <charconv>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <iterator>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
@@ -488,23 +483,25 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize
|
||||
if (!hAdvapi32)
|
||||
hAdvapi32 = LoadLibrary(TEXT("advapi32.dll"));
|
||||
|
||||
auto fun6 = fun6_t((void (*)()) GetProcAddress(hAdvapi32, "OpenProcessToken"));
|
||||
if (!fun6)
|
||||
auto OpenProcessToken_f =
|
||||
OpenProcessToken_t((void (*)()) GetProcAddress(hAdvapi32, "OpenProcessToken"));
|
||||
if (!OpenProcessToken_f)
|
||||
return nullptr;
|
||||
auto fun7 = fun7_t((void (*)()) GetProcAddress(hAdvapi32, "LookupPrivilegeValueA"));
|
||||
if (!fun7)
|
||||
auto LookupPrivilegeValueA_f =
|
||||
LookupPrivilegeValueA_t((void (*)()) GetProcAddress(hAdvapi32, "LookupPrivilegeValueA"));
|
||||
if (!LookupPrivilegeValueA_f)
|
||||
return nullptr;
|
||||
auto fun8 = fun8_t((void (*)()) GetProcAddress(hAdvapi32, "AdjustTokenPrivileges"));
|
||||
if (!fun8)
|
||||
auto AdjustTokenPrivileges_f =
|
||||
AdjustTokenPrivileges_t((void (*)()) GetProcAddress(hAdvapi32, "AdjustTokenPrivileges"));
|
||||
if (!AdjustTokenPrivileges_f)
|
||||
return nullptr;
|
||||
|
||||
// We need SeLockMemoryPrivilege, so try to enable it for the process
|
||||
if (!fun6( // OpenProcessToken()
|
||||
if (!OpenProcessToken_f( // OpenProcessToken()
|
||||
GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken))
|
||||
return nullptr;
|
||||
|
||||
if (fun7( // LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &luid)
|
||||
nullptr, "SeLockMemoryPrivilege", &luid))
|
||||
if (LookupPrivilegeValueA_f(nullptr, "SeLockMemoryPrivilege", &luid))
|
||||
{
|
||||
TOKEN_PRIVILEGES tp{};
|
||||
TOKEN_PRIVILEGES prevTp{};
|
||||
@@ -516,8 +513,8 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize
|
||||
|
||||
// Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds,
|
||||
// we still need to query GetLastError() to ensure that the privileges were actually obtained.
|
||||
if (fun8( // AdjustTokenPrivileges()
|
||||
hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen)
|
||||
if (AdjustTokenPrivileges_f(hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp,
|
||||
&prevTpLen)
|
||||
&& GetLastError() == ERROR_SUCCESS)
|
||||
{
|
||||
// Round up size to full pages and allocate
|
||||
@@ -526,8 +523,7 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize
|
||||
PAGE_READWRITE);
|
||||
|
||||
// Privilege no longer needed, restore previous state
|
||||
fun8( // AdjustTokenPrivileges ()
|
||||
hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr);
|
||||
AdjustTokenPrivileges_f(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -603,13 +599,21 @@ void aligned_large_pages_free(void* mem) { std_aligned_free(mem); }
|
||||
#endif
|
||||
|
||||
size_t str_to_size_t(const std::string& s) {
|
||||
size_t value;
|
||||
auto result = std::from_chars(s.data(), s.data() + s.size(), value);
|
||||
|
||||
if (result.ec != std::errc())
|
||||
unsigned long long value = std::stoull(s);
|
||||
if (value > std::numeric_limits<size_t>::max())
|
||||
std::exit(EXIT_FAILURE);
|
||||
return static_cast<size_t>(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
std::optional<std::string> read_file_to_string(const std::string& path) {
|
||||
std::ifstream f(path, std::ios_base::binary);
|
||||
if (!f)
|
||||
return std::nullopt;
|
||||
return std::string(std::istreambuf_iterator<char>(f), std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
void remove_whitespace(std::string& s) {
|
||||
s.erase(std::remove_if(s.begin(), s.end(), [](char c) { return std::isspace(c); }), s.end());
|
||||
}
|
||||
|
||||
std::string CommandLine::get_binary_directory(std::string argv0) {
|
||||
|
||||
+12
-16
@@ -77,6 +77,8 @@ using AlignedPtr = std::unique_ptr<T, AlignedDeleter<T>>;
|
||||
template<typename T>
|
||||
using LargePagePtr = std::unique_ptr<T, LargePageDeleter<T>>;
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
struct PipeDeleter {
|
||||
void operator()(FILE* file) const {
|
||||
if (file != nullptr)
|
||||
@@ -86,23 +88,12 @@ struct PipeDeleter {
|
||||
}
|
||||
};
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
inline std::optional<std::string> get_system_command_output(const std::string& command) {
|
||||
std::unique_ptr<FILE, PipeDeleter> pipe(popen(command.c_str(), "r"));
|
||||
if (!pipe)
|
||||
return std::nullopt;
|
||||
|
||||
std::string result;
|
||||
char buffer[1024];
|
||||
while (fgets(buffer, sizeof(buffer), pipe.get()) != nullptr)
|
||||
result += buffer;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Reads the file as bytes.
|
||||
// Returns std::nullopt if the file does not exist.
|
||||
std::optional<std::string> read_file_to_string(const std::string& path);
|
||||
|
||||
void dbg_hit_on(bool cond, int slot = 0);
|
||||
void dbg_mean_of(int64_t value, int slot = 0);
|
||||
void dbg_stdev_of(int64_t value, int slot = 0);
|
||||
@@ -118,9 +109,12 @@ inline TimePoint now() {
|
||||
}
|
||||
|
||||
inline std::vector<std::string> split(const std::string& s, const std::string& delimiter) {
|
||||
size_t begin = 0;
|
||||
std::vector<std::string> res;
|
||||
|
||||
if (s.empty())
|
||||
return res;
|
||||
|
||||
size_t begin = 0;
|
||||
for (;;)
|
||||
{
|
||||
const size_t end = s.find(delimiter, begin);
|
||||
@@ -136,6 +130,8 @@ inline std::vector<std::string> split(const std::string& s, const std::string& d
|
||||
return res;
|
||||
}
|
||||
|
||||
void remove_whitespace(std::string& s);
|
||||
|
||||
enum SyncCout {
|
||||
IO_LOCK,
|
||||
IO_UNLOCK
|
||||
|
||||
+4
-8
@@ -178,7 +178,7 @@ void MovePicker::score() {
|
||||
Square to = m.to_sq();
|
||||
|
||||
// histories
|
||||
m.value = 2 * (*mainHistory)[pos.side_to_move()][m.from_to()];
|
||||
m.value = (*mainHistory)[pos.side_to_move()][m.from_to()];
|
||||
m.value += 2 * (*pawnHistory)[pawn_structure_index(pos)][pc][to];
|
||||
m.value += 2 * (*continuationHistory[0])[pc][to];
|
||||
m.value += (*continuationHistory[1])[pc][to];
|
||||
@@ -197,13 +197,9 @@ void MovePicker::score() {
|
||||
: 0;
|
||||
|
||||
// malus for putting piece en prise
|
||||
m.value -= !(threatenedPieces & from)
|
||||
? (pt == QUEEN ? bool(to & threatenedByRook) * 48150
|
||||
+ bool(to & threatenedByMinor) * 10650
|
||||
: pt == ROOK ? bool(to & threatenedByMinor) * 24335
|
||||
: pt != PAWN ? bool(to & threatenedByPawn) * 14950
|
||||
: 0)
|
||||
: 0;
|
||||
m.value -= (pt == QUEEN ? bool(to & threatenedByRook) * 49000
|
||||
: pt == ROOK ? bool(to & threatenedByMinor) * 24335
|
||||
: bool(to & threatenedByPawn) * 14900);
|
||||
}
|
||||
|
||||
else // Type == EVASIONS
|
||||
|
||||
+282
-165
@@ -33,15 +33,19 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// We support linux very well, but we explicitly do NOT support Android, partially because
|
||||
// there are potential issues with `lscpu`, `popen` availability, and partially because
|
||||
// there's no NUMA environments running Android and there probably won't be.
|
||||
// We support linux very well, but we explicitly do NOT support Android, because there's
|
||||
// no affected systems, not worth maintaining.
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
#if !defined(_GNU_SOURCE)
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
#include <sched.h>
|
||||
#elif defined(_WIN32)
|
||||
#elif defined(_WIN64)
|
||||
|
||||
#if _WIN32_WINNT < 0x0601
|
||||
#undef _WIN32_WINNT
|
||||
#define _WIN32_WINNT 0x0601 // Force to include needed API prototypes
|
||||
#endif
|
||||
|
||||
// On Windows each processor group can have up to 64 processors.
|
||||
// https://learn.microsoft.com/en-us/windows/win32/procthread/processor-groups
|
||||
@@ -51,6 +55,9 @@ static constexpr size_t WIN_PROCESSOR_GROUP_SIZE = 64;
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#if defined small
|
||||
#undef small
|
||||
#endif
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadselectedcpusetmasks
|
||||
using SetThreadSelectedCpuSetMasks_t = BOOL (*)(HANDLE, PGROUP_AFFINITY, USHORT);
|
||||
@@ -58,6 +65,18 @@ using SetThreadSelectedCpuSetMasks_t = BOOL (*)(HANDLE, PGROUP_AFFINITY, USHORT)
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/processtopologyapi/nf-processtopologyapi-setthreadgroupaffinity
|
||||
using SetThreadGroupAffinity_t = BOOL (*)(HANDLE, const GROUP_AFFINITY*, PGROUP_AFFINITY);
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadselectedcpusetmasks
|
||||
using GetThreadSelectedCpuSetMasks_t = BOOL (*)(HANDLE, PGROUP_AFFINITY, USHORT, PUSHORT);
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getprocessaffinitymask
|
||||
using GetProcessAffinityMask_t = BOOL (*)(HANDLE, PDWORD_PTR, PDWORD_PTR);
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/processtopologyapi/nf-processtopologyapi-getprocessgroupaffinity
|
||||
using GetProcessGroupAffinity_t = BOOL (*)(HANDLE, PUSHORT, PUSHORT);
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getactiveprocessorcount
|
||||
using GetActiveProcessorCount_t = DWORD (*)(WORD);
|
||||
|
||||
#endif
|
||||
|
||||
#include "misc.h"
|
||||
@@ -67,8 +86,28 @@ namespace Stockfish {
|
||||
using CpuIndex = size_t;
|
||||
using NumaIndex = size_t;
|
||||
|
||||
inline const CpuIndex SYSTEM_THREADS_NB =
|
||||
std::max<CpuIndex>(1, std::thread::hardware_concurrency());
|
||||
inline CpuIndex get_hardware_concurrency() {
|
||||
CpuIndex concurrency = std::thread::hardware_concurrency();
|
||||
|
||||
// Get all processors across all processor groups on windows, since ::hardware_concurrency
|
||||
// only returns the number of processors in the first group, because only these
|
||||
// are available to std::thread.
|
||||
#ifdef _WIN64
|
||||
HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll"));
|
||||
auto GetActiveProcessorCount_f =
|
||||
GetActiveProcessorCount_t((void (*)()) GetProcAddress(k32, "GetActiveProcessorCount"));
|
||||
|
||||
if (GetActiveProcessorCount_f != nullptr)
|
||||
{
|
||||
concurrency = GetActiveProcessorCount_f(ALL_PROCESSOR_GROUPS);
|
||||
}
|
||||
#endif
|
||||
|
||||
return concurrency;
|
||||
}
|
||||
|
||||
inline const CpuIndex SYSTEM_THREADS_NB = std::max<CpuIndex>(1, get_hardware_concurrency());
|
||||
|
||||
|
||||
// We want to abstract the purpose of storing the numa node index somewhat.
|
||||
// Whoever is using this does not need to know the specifics of the replication
|
||||
@@ -91,8 +130,6 @@ class NumaReplicatedAccessToken {
|
||||
// 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.
|
||||
// NOTE: the numbering is only valid within the process, as for example on Windows
|
||||
// every process gets a "virtualized" set of processors that respects the current affinity
|
||||
// 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, i.e. every node exposed by NumaConfig
|
||||
@@ -109,144 +146,91 @@ class NumaConfig {
|
||||
add_cpu_range_to_node(NumaIndex{0}, CpuIndex{0}, numCpus - 1);
|
||||
}
|
||||
|
||||
static std::set<CpuIndex> get_process_affinity() {
|
||||
std::set<CpuIndex> cpus;
|
||||
|
||||
// For unsupported systems, or in case of a soft error, we may assume all processors
|
||||
// are available for use.
|
||||
[[maybe_unused]] auto set_to_all_cpus = [&]() {
|
||||
for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c)
|
||||
cpus.insert(c);
|
||||
};
|
||||
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
|
||||
// cpu_set_t by default holds 1024 entries. This may not be enough soon,
|
||||
// but there is no easy way to determine how many threads there actually is.
|
||||
// In this case we just choose a reasonable upper bound.
|
||||
static constexpr CpuIndex MaxNumCpus = 1024 * 64;
|
||||
|
||||
cpu_set_t* mask = CPU_ALLOC(MaxNumCpus);
|
||||
if (mask == nullptr)
|
||||
std::exit(EXIT_FAILURE);
|
||||
|
||||
const size_t masksize = CPU_ALLOC_SIZE(MaxNumCpus);
|
||||
|
||||
CPU_ZERO_S(masksize, mask);
|
||||
|
||||
const int status = sched_getaffinity(0, masksize, mask);
|
||||
|
||||
if (status != 0)
|
||||
{
|
||||
CPU_FREE(mask);
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
for (CpuIndex c = 0; c < MaxNumCpus; ++c)
|
||||
if (CPU_ISSET_S(c, masksize, mask))
|
||||
cpus.insert(c);
|
||||
|
||||
CPU_FREE(mask);
|
||||
|
||||
#elif defined(_WIN32)
|
||||
|
||||
// Windows is problematic and weird due to multiple ways of setting affinity, processor groups,
|
||||
// and behaviour changes between versions. It's unclear if we can support this feature
|
||||
// on Windows in the same way we do on Linux.
|
||||
// Apparently when affinity is set via either start /affinity or msys2 taskset
|
||||
// the function GetNumaProcessorNodeEx completely disregards the processors that we do not
|
||||
// have affinity more. Moreover, the indices are shifted to start from 0, indicating that Windows
|
||||
// is providing a whole new mapping of processors to this process. This is problematic in some cases
|
||||
// but it at least allows us to [probably] support this affinity restriction feature by default.
|
||||
// So overall, Windows appears to "virtualize" a set of processors and processor groups for every
|
||||
// process. It's unclear if this assignment can change while the process is running.
|
||||
// std::thread::hardware_concurrency() returns the number of processors that's consistent
|
||||
// with GetNumaProcessorNodeEx, so we can just add all of them.
|
||||
|
||||
set_to_all_cpus();
|
||||
|
||||
#else
|
||||
|
||||
// For other systems we assume the process is allowed to execute on all processors.
|
||||
set_to_all_cpus();
|
||||
|
||||
#endif
|
||||
|
||||
return cpus;
|
||||
}
|
||||
|
||||
// This function queries the system for the mapping of processors to NUMA nodes.
|
||||
// On Linux we utilize `lscpu` to avoid libnuma.
|
||||
// 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(bool respectProcessAffinity = true) {
|
||||
static NumaConfig from_system([[maybe_unused]] bool respectProcessAffinity = true) {
|
||||
NumaConfig cfg = empty();
|
||||
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
|
||||
std::set<CpuIndex> allowedCpus;
|
||||
|
||||
if (respectProcessAffinity)
|
||||
allowedCpus = get_process_affinity();
|
||||
else
|
||||
{
|
||||
for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c)
|
||||
allowedCpus.insert(c);
|
||||
}
|
||||
|
||||
auto is_cpu_allowed = [&](CpuIndex c) { return allowedCpus.count(c) == 1; };
|
||||
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
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.
|
||||
// This command produces output in the following form
|
||||
// CPU NODE
|
||||
// 0 0
|
||||
// 1 0
|
||||
// 2 1
|
||||
// 3 1
|
||||
//
|
||||
// On some systems it may use '-' to signify no NUMA node, in which case we assume it's in node 0.
|
||||
auto lscpuOpt = get_system_command_output("lscpu -e=cpu,node");
|
||||
if (lscpuOpt.has_value())
|
||||
|
||||
// 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())
|
||||
{
|
||||
|
||||
std::istringstream ss(*lscpuOpt);
|
||||
|
||||
// skip the list header
|
||||
ss.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
|
||||
|
||||
while (true)
|
||||
{
|
||||
CpuIndex c;
|
||||
NumaIndex n;
|
||||
|
||||
ss >> c;
|
||||
|
||||
if (!ss)
|
||||
break;
|
||||
|
||||
ss >> n;
|
||||
|
||||
if (!ss)
|
||||
{
|
||||
ss.clear();
|
||||
std::string dummy;
|
||||
ss >> dummy;
|
||||
n = 0;
|
||||
}
|
||||
|
||||
if (is_cpu_allowed(c))
|
||||
cfg.add_cpu_to_node(n, c);
|
||||
}
|
||||
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(_WIN32)
|
||||
#elif defined(_WIN64)
|
||||
|
||||
std::optional<std::set<CpuIndex>> allowedCpus;
|
||||
|
||||
if (respectProcessAffinity)
|
||||
allowedCpus = get_process_affinity();
|
||||
|
||||
// The affinity can't be determined in all cases on Windows, but we at least guarantee
|
||||
// that the number of allowed processors is >= number of processors in the affinity mask.
|
||||
// In case the user is not satisfied they must set the processor numbers explicitly.
|
||||
auto is_cpu_allowed = [&allowedCpus](CpuIndex c) {
|
||||
return !allowedCpus.has_value() || allowedCpus->count(c) == 1;
|
||||
};
|
||||
|
||||
// Since Windows 11 and Windows Server 2022 thread affinities can span
|
||||
// processor groups and can be set as such by a new WinAPI function.
|
||||
@@ -268,14 +252,6 @@ class NumaConfig {
|
||||
procnum.Reserved = 0;
|
||||
USHORT nodeNumber;
|
||||
|
||||
// When start /affinity or taskset was used to run this process with restricted affinity
|
||||
// GetNumaProcessorNodeEx will NOT correspond to the system's processor setup, instead
|
||||
// it appears to follow a completely new processor assignment, made specifically for this process,
|
||||
// in which processors that this process has affinity for are remapped, and only those are remapped,
|
||||
// to form a new set of processors. In other words, we can only get processors
|
||||
// which we have affinity for this way. This means that the behaviour for
|
||||
// `respectProcessAffinity == false` may be unexpected when affinity is set from outside,
|
||||
// while the behaviour for `respectProcessAffinity == true` is given by default.
|
||||
const BOOL status = GetNumaProcessorNodeEx(&procnum, &nodeNumber);
|
||||
const CpuIndex c = static_cast<CpuIndex>(procGroup) * WIN_PROCESSOR_GROUP_SIZE
|
||||
+ static_cast<CpuIndex>(number);
|
||||
@@ -323,8 +299,7 @@ class NumaConfig {
|
||||
|
||||
// Fallback for unsupported systems.
|
||||
for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c)
|
||||
if (is_cpu_allowed(c))
|
||||
cfg.add_cpu_to_node(NumaIndex{0}, c);
|
||||
cfg.add_cpu_to_node(NumaIndex{0}, c);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -344,38 +319,17 @@ class NumaConfig {
|
||||
NumaIndex n = 0;
|
||||
for (auto&& nodeStr : split(s, ":"))
|
||||
{
|
||||
bool addedAnyCpuInThisNode = false;
|
||||
|
||||
for (const std::string& cpuStr : split(nodeStr, ","))
|
||||
auto indices = indices_from_shortened_string(nodeStr);
|
||||
if (!indices.empty())
|
||||
{
|
||||
if (cpuStr.empty())
|
||||
continue;
|
||||
|
||||
auto parts = split(cpuStr, "-");
|
||||
if (parts.size() == 1)
|
||||
for (auto idx : indices)
|
||||
{
|
||||
const CpuIndex c = CpuIndex{str_to_size_t(parts[0])};
|
||||
if (!cfg.add_cpu_to_node(n, c))
|
||||
if (!cfg.add_cpu_to_node(n, CpuIndex(idx)))
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
else if (parts.size() == 2)
|
||||
{
|
||||
const CpuIndex cfirst = CpuIndex{str_to_size_t(parts[0])};
|
||||
const CpuIndex clast = CpuIndex{str_to_size_t(parts[1])};
|
||||
|
||||
if (!cfg.add_cpu_range_to_node(n, cfirst, clast))
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
addedAnyCpuInThisNode = true;
|
||||
}
|
||||
|
||||
if (addedAnyCpuInThisNode)
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
|
||||
cfg.customAffinity = true;
|
||||
@@ -549,7 +503,7 @@ class NumaConfig {
|
||||
// This is defensive, allowed because this code is not performance critical.
|
||||
sched_yield();
|
||||
|
||||
#elif defined(_WIN32)
|
||||
#elif defined(_WIN64)
|
||||
|
||||
// Requires Windows 11. No good way to set thread affinity spanning processor groups before that.
|
||||
HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll"));
|
||||
@@ -561,8 +515,8 @@ class NumaConfig {
|
||||
if (SetThreadSelectedCpuSetMasks_f != nullptr)
|
||||
{
|
||||
// Only available on Windows 11 and Windows Server 2022 onwards.
|
||||
const USHORT numProcGroups =
|
||||
((highestCpuIndex + 1) + WIN_PROCESSOR_GROUP_SIZE - 1) / WIN_PROCESSOR_GROUP_SIZE;
|
||||
const USHORT numProcGroups = USHORT(
|
||||
((highestCpuIndex + 1) + WIN_PROCESSOR_GROUP_SIZE - 1) / WIN_PROCESSOR_GROUP_SIZE);
|
||||
auto groupAffinities = std::make_unique<GROUP_AFFINITY[]>(numProcGroups);
|
||||
std::memset(groupAffinities.get(), 0, sizeof(GROUP_AFFINITY) * numProcGroups);
|
||||
for (WORD i = 0; i < numProcGroups; ++i)
|
||||
@@ -603,14 +557,14 @@ class NumaConfig {
|
||||
// See https://learn.microsoft.com/en-us/windows/win32/procthread/numa-support
|
||||
GROUP_AFFINITY affinity;
|
||||
std::memset(&affinity, 0, sizeof(GROUP_AFFINITY));
|
||||
affinity.Group = static_cast<WORD>(n);
|
||||
// We use an ordered set so we're guaranteed to get the smallest cpu number here.
|
||||
const size_t forcedProcGroupIndex = *(nodes[n].begin()) / WIN_PROCESSOR_GROUP_SIZE;
|
||||
affinity.Group = static_cast<WORD>(forcedProcGroupIndex);
|
||||
for (CpuIndex c : nodes[n])
|
||||
{
|
||||
const size_t procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE;
|
||||
const size_t idxWithinProcGroup = c % WIN_PROCESSOR_GROUP_SIZE;
|
||||
// We skip processors that are not in the same proccessor group.
|
||||
// We skip processors that are not in the same processor group.
|
||||
// If everything was set up correctly this will never be an issue,
|
||||
// but we have to account for bad NUMA node specification.
|
||||
if (procGroupIndex != forcedProcGroupIndex)
|
||||
@@ -709,6 +663,169 @@ class NumaConfig {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
|
||||
static std::set<CpuIndex> get_process_affinity() {
|
||||
|
||||
std::set<CpuIndex> cpus;
|
||||
|
||||
// For unsupported systems, or in case of a soft error, we may assume all processors
|
||||
// are available for use.
|
||||
[[maybe_unused]] auto set_to_all_cpus = [&]() {
|
||||
for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c)
|
||||
cpus.insert(c);
|
||||
};
|
||||
|
||||
// cpu_set_t by default holds 1024 entries. This may not be enough soon,
|
||||
// but there is no easy way to determine how many threads there actually is.
|
||||
// In this case we just choose a reasonable upper bound.
|
||||
static constexpr CpuIndex MaxNumCpus = 1024 * 64;
|
||||
|
||||
cpu_set_t* mask = CPU_ALLOC(MaxNumCpus);
|
||||
if (mask == nullptr)
|
||||
std::exit(EXIT_FAILURE);
|
||||
|
||||
const size_t masksize = CPU_ALLOC_SIZE(MaxNumCpus);
|
||||
|
||||
CPU_ZERO_S(masksize, mask);
|
||||
|
||||
const int status = sched_getaffinity(0, masksize, mask);
|
||||
|
||||
if (status != 0)
|
||||
{
|
||||
CPU_FREE(mask);
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
for (CpuIndex c = 0; c < MaxNumCpus; ++c)
|
||||
if (CPU_ISSET_S(c, masksize, mask))
|
||||
cpus.insert(c);
|
||||
|
||||
CPU_FREE(mask);
|
||||
|
||||
return cpus;
|
||||
}
|
||||
|
||||
#elif defined(_WIN64)
|
||||
|
||||
// On Windows there are two ways to set affinity, and therefore 2 ways to get it.
|
||||
// These are not consistent, so we have to check both.
|
||||
// In some cases it is actually not possible to determine affinity.
|
||||
// For example when two different threads have affinity on different processor groups,
|
||||
// set using SetThreadAffinityMask, we can't retrieve the actual affinities.
|
||||
// From documentation on GetProcessAffinityMask:
|
||||
// > If the calling process contains threads in multiple groups,
|
||||
// > the function returns zero for both affinity masks.
|
||||
// In such cases we just give up and assume we have affinity for all processors.
|
||||
// nullopt means no affinity is set, that is, all processors are allowed
|
||||
static std::optional<std::set<CpuIndex>> get_process_affinity() {
|
||||
HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll"));
|
||||
auto GetThreadSelectedCpuSetMasks_f = GetThreadSelectedCpuSetMasks_t(
|
||||
(void (*)()) GetProcAddress(k32, "GetThreadSelectedCpuSetMasks"));
|
||||
auto GetProcessAffinityMask_f =
|
||||
GetProcessAffinityMask_t((void (*)()) GetProcAddress(k32, "GetProcessAffinityMask"));
|
||||
auto GetProcessGroupAffinity_f =
|
||||
GetProcessGroupAffinity_t((void (*)()) GetProcAddress(k32, "GetProcessGroupAffinity"));
|
||||
|
||||
if (GetThreadSelectedCpuSetMasks_f != nullptr)
|
||||
{
|
||||
std::set<CpuIndex> cpus;
|
||||
|
||||
USHORT RequiredMaskCount;
|
||||
GetThreadSelectedCpuSetMasks_f(GetCurrentThread(), nullptr, 0, &RequiredMaskCount);
|
||||
|
||||
// If RequiredMaskCount then these affinities were never set, but it's not consistent
|
||||
// so GetProcessAffinityMask may still return some affinity.
|
||||
if (RequiredMaskCount > 0)
|
||||
{
|
||||
auto groupAffinities = std::make_unique<GROUP_AFFINITY[]>(RequiredMaskCount);
|
||||
|
||||
GetThreadSelectedCpuSetMasks_f(GetCurrentThread(), groupAffinities.get(),
|
||||
RequiredMaskCount, &RequiredMaskCount);
|
||||
|
||||
for (USHORT i = 0; i < RequiredMaskCount; ++i)
|
||||
{
|
||||
const size_t procGroupIndex = groupAffinities[i].Group;
|
||||
|
||||
for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j)
|
||||
{
|
||||
if (groupAffinities[i].Mask & (KAFFINITY(1) << j))
|
||||
cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j);
|
||||
}
|
||||
}
|
||||
|
||||
return cpus;
|
||||
}
|
||||
}
|
||||
|
||||
if (GetProcessAffinityMask_f != nullptr && GetProcessGroupAffinity_f != nullptr)
|
||||
{
|
||||
std::set<CpuIndex> cpus;
|
||||
|
||||
DWORD_PTR proc, sys;
|
||||
BOOL status = GetProcessAffinityMask_f(GetCurrentProcess(), &proc, &sys);
|
||||
if (status == 0)
|
||||
return std::nullopt;
|
||||
|
||||
// We can't determine affinity because it spans processor groups.
|
||||
if (proc == 0)
|
||||
return std::nullopt;
|
||||
|
||||
// We are expecting a single group.
|
||||
USHORT GroupCount = 1;
|
||||
alignas(4) USHORT GroupArray[1];
|
||||
status = GetProcessGroupAffinity_f(GetCurrentProcess(), &GroupCount, GroupArray);
|
||||
if (status == 0 || GroupCount != 1)
|
||||
return std::nullopt;
|
||||
|
||||
const size_t procGroupIndex = GroupArray[0];
|
||||
|
||||
uint64_t mask = static_cast<uint64_t>(proc);
|
||||
for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j)
|
||||
{
|
||||
if (mask & (KAFFINITY(1) << j))
|
||||
cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j);
|
||||
}
|
||||
|
||||
return cpus;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static std::vector<size_t> indices_from_shortened_string(const std::string& s) {
|
||||
std::vector<size_t> indices;
|
||||
|
||||
if (s.empty())
|
||||
return indices;
|
||||
|
||||
for (const std::string& ss : split(s, ","))
|
||||
{
|
||||
if (ss.empty())
|
||||
continue;
|
||||
|
||||
auto parts = split(ss, "-");
|
||||
if (parts.size() == 1)
|
||||
{
|
||||
const CpuIndex c = CpuIndex{str_to_size_t(parts[0])};
|
||||
indices.emplace_back(c);
|
||||
}
|
||||
else if (parts.size() == 2)
|
||||
{
|
||||
const CpuIndex cfirst = CpuIndex{str_to_size_t(parts[0])};
|
||||
const CpuIndex clast = CpuIndex{str_to_size_t(parts[1])};
|
||||
for (size_t c = cfirst; c <= clast; ++c)
|
||||
{
|
||||
indices.emplace_back(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
};
|
||||
|
||||
class NumaReplicationContext;
|
||||
|
||||
+25
-20
@@ -162,7 +162,7 @@ void Search::Worker::start_searching() {
|
||||
}
|
||||
|
||||
main_manager()->tm.init(limits, rootPos.side_to_move(), rootPos.game_ply(), options,
|
||||
main_manager()->originalPly);
|
||||
main_manager()->originalTimeAdjust);
|
||||
tt.new_search();
|
||||
|
||||
if (rootMoves.empty())
|
||||
@@ -598,11 +598,12 @@ Value Search::Worker::search(
|
||||
Key posKey;
|
||||
Move ttMove, move, excludedMove, bestMove;
|
||||
Depth extension, newDepth;
|
||||
Value bestValue, value, ttValue, eval, maxValue, probCutBeta;
|
||||
Value bestValue, value, ttValue, eval, maxValue, probCutBeta, singularValue;
|
||||
bool givesCheck, improving, priorCapture, opponentWorsening;
|
||||
bool capture, moveCountPruning, ttCapture;
|
||||
Piece movedPiece;
|
||||
int moveCount, captureCount, quietCount;
|
||||
Bound singularBound;
|
||||
|
||||
// Step 1. Initialize node
|
||||
Worker* thisThread = this;
|
||||
@@ -667,7 +668,7 @@ Value Search::Worker::search(
|
||||
ss->ttPv = PvNode || (ss->ttHit && tte->is_pv());
|
||||
|
||||
// At non-PV nodes we check for an early TT cutoff
|
||||
if (!PvNode && !excludedMove && tte->depth() > depth
|
||||
if (!PvNode && !excludedMove && tte->depth() > depth - (ttValue <= beta)
|
||||
&& ttValue != VALUE_NONE // Possible in case of TT access race or if !ttHit
|
||||
&& (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER)))
|
||||
{
|
||||
@@ -973,6 +974,8 @@ moves_loop: // When in check, search starts here
|
||||
|
||||
value = bestValue;
|
||||
moveCountPruning = false;
|
||||
singularValue = VALUE_INFINITE;
|
||||
singularBound = BOUND_NONE;
|
||||
|
||||
// Step 13. Loop through all pseudo-legal moves until no moves remain
|
||||
// or a beta cutoff occurs.
|
||||
@@ -1022,7 +1025,9 @@ moves_loop: // When in check, search starts here
|
||||
if (!rootNode && pos.non_pawn_material(us) && bestValue > VALUE_TB_LOSS_IN_MAX_PLY)
|
||||
{
|
||||
// Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo)
|
||||
moveCountPruning = moveCount >= futility_move_count(improving, depth);
|
||||
moveCountPruning =
|
||||
moveCount >= futility_move_count(improving, depth)
|
||||
- (singularBound == BOUND_UPPER && singularValue < alpha - 50);
|
||||
|
||||
// Reduced depth of the next LMR search
|
||||
int lmrDepth = newDepth - r;
|
||||
@@ -1108,19 +1113,18 @@ moves_loop: // When in check, search starts here
|
||||
Depth singularDepth = newDepth / 2;
|
||||
|
||||
ss->excludedMove = move;
|
||||
value =
|
||||
value = singularValue =
|
||||
search<NonPV>(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode);
|
||||
singularBound = singularValue >= singularBeta ? BOUND_LOWER : BOUND_UPPER;
|
||||
ss->excludedMove = Move::none();
|
||||
|
||||
if (value < singularBeta)
|
||||
{
|
||||
int doubleMargin = 304 * PvNode - 203 * !ttCapture;
|
||||
int tripleMargin = 117 + 259 * PvNode - 296 * !ttCapture + 97 * ss->ttPv;
|
||||
int quadMargin = 486 + 343 * PvNode - 273 * !ttCapture + 232 * ss->ttPv;
|
||||
|
||||
extension = 1 + (value < singularBeta - doubleMargin)
|
||||
+ (value < singularBeta - tripleMargin)
|
||||
+ (value < singularBeta - quadMargin);
|
||||
+ (value < singularBeta - tripleMargin);
|
||||
|
||||
depth += ((!PvNode) && (depth < 16));
|
||||
}
|
||||
@@ -1201,17 +1205,17 @@ moves_loop: // When in check, search starts here
|
||||
if ((ss + 1)->cutoffCnt > 3)
|
||||
r++;
|
||||
|
||||
// Set reduction to 0 for first picked move (ttMove) (~2 Elo)
|
||||
// Nullifies all previous reduction adjustments to ttMove and leaves only history to do them
|
||||
// For first picked move (ttMove) reduce reduction
|
||||
// but never allow it to go below 0 (~3 Elo)
|
||||
else if (move == ttMove)
|
||||
r = 0;
|
||||
r = std::max(0, r - 2);
|
||||
|
||||
ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()]
|
||||
+ (*contHist[0])[movedPiece][move.to_sq()]
|
||||
+ (*contHist[1])[movedPiece][move.to_sq()] - 5169;
|
||||
|
||||
// Decrease/increase reduction for moves with a good/bad history (~8 Elo)
|
||||
r -= ss->statScore / (12219 - std::min(depth, 13) * 120);
|
||||
r -= ss->statScore / 11049;
|
||||
|
||||
// Step 17. Late moves reduction / extension (LMR, ~117 Elo)
|
||||
if (depth >= 2 && moveCount > 1 + rootNode)
|
||||
@@ -1341,7 +1345,7 @@ moves_loop: // When in check, search starts here
|
||||
|
||||
if (value >= beta)
|
||||
{
|
||||
ss->cutoffCnt += 1 + !ttMove;
|
||||
ss->cutoffCnt += 1 + !ttMove - (extension >= 2);
|
||||
assert(value >= beta); // Fail high
|
||||
break;
|
||||
}
|
||||
@@ -1391,18 +1395,19 @@ moves_loop: // When in check, search starts here
|
||||
// Bonus for prior countermove that caused the fail low
|
||||
else if (!priorCapture && prevSq != SQ_NONE)
|
||||
{
|
||||
int bonus = (depth > 4) + (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14144)
|
||||
+ ((ss - 1)->moveCount > 9) + (!ss->inCheck && bestValue <= ss->staticEval - 115)
|
||||
+ (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 81);
|
||||
int bonus = (116 * (depth > 5) + 115 * (PvNode || cutNode)
|
||||
+ 186 * ((ss - 1)->statScore < -14144) + 121 * ((ss - 1)->moveCount > 9)
|
||||
+ 64 * (!ss->inCheck && bestValue <= ss->staticEval - 115)
|
||||
+ 137 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 81));
|
||||
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq,
|
||||
stat_bonus(depth) * bonus);
|
||||
stat_bonus(depth) * bonus / 100);
|
||||
thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()]
|
||||
<< stat_bonus(depth) * bonus / 2;
|
||||
<< stat_bonus(depth) * bonus / 200;
|
||||
|
||||
|
||||
if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION)
|
||||
thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq]
|
||||
<< stat_bonus(depth) * bonus * 4;
|
||||
<< stat_bonus(depth) * bonus / 25;
|
||||
}
|
||||
|
||||
if (PvNode)
|
||||
@@ -1536,7 +1541,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
|
||||
to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos);
|
||||
|
||||
// ttValue can be used as a better position evaluation (~13 Elo)
|
||||
if (ttValue != VALUE_NONE
|
||||
if (std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY
|
||||
&& (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER)))
|
||||
bestValue = ttValue;
|
||||
}
|
||||
|
||||
+1
-1
@@ -218,7 +218,7 @@ class SearchManager: public ISearchManager {
|
||||
Depth depth) const;
|
||||
|
||||
Stockfish::TimeManagement tm;
|
||||
int originalPly;
|
||||
double originalTimeAdjust;
|
||||
int callsCnt;
|
||||
std::atomic_bool ponder;
|
||||
|
||||
|
||||
+6
-4
@@ -216,11 +216,13 @@ void ThreadPool::clear() {
|
||||
for (auto&& th : threads)
|
||||
th->wait_for_search_finished();
|
||||
|
||||
main_manager()->callsCnt = 0;
|
||||
main_manager()->bestPreviousScore = VALUE_INFINITE;
|
||||
// These two affect the time taken on the first move of a game:
|
||||
main_manager()->bestPreviousAverageScore = VALUE_INFINITE;
|
||||
main_manager()->originalPly = -1;
|
||||
main_manager()->previousTimeReduction = 1.0;
|
||||
main_manager()->previousTimeReduction = 0.85;
|
||||
|
||||
main_manager()->callsCnt = 0;
|
||||
main_manager()->bestPreviousScore = VALUE_INFINITE;
|
||||
main_manager()->originalTimeAdjust = -1;
|
||||
main_manager()->tm.clear();
|
||||
}
|
||||
|
||||
|
||||
+10
-10
@@ -44,8 +44,11 @@ void TimeManagement::advance_nodes_time(std::int64_t nodes) {
|
||||
// the bounds of time allowed for the current game ply. We currently support:
|
||||
// 1) x basetime (+ z increment)
|
||||
// 2) x moves in y seconds (+ z increment)
|
||||
void TimeManagement::init(
|
||||
Search::LimitsType& limits, Color us, int ply, const OptionsMap& options, int& originalPly) {
|
||||
void TimeManagement::init(Search::LimitsType& limits,
|
||||
Color us,
|
||||
int ply,
|
||||
const OptionsMap& options,
|
||||
double& originalTimeAdjust) {
|
||||
TimePoint npmsec = TimePoint(options["nodestime"]);
|
||||
|
||||
// If we have no time, we don't need to fully initialize TM.
|
||||
@@ -56,9 +59,6 @@ void TimeManagement::init(
|
||||
if (limits.time[us] == 0)
|
||||
return;
|
||||
|
||||
if (originalPly == -1)
|
||||
originalPly = ply;
|
||||
|
||||
TimePoint moveOverhead = TimePoint(options["Move Overhead"]);
|
||||
|
||||
// optScale is a percentage of available time to use for the current move.
|
||||
@@ -105,10 +105,9 @@ void TimeManagement::init(
|
||||
// game time for the current move, so also cap to a percentage of available game time.
|
||||
if (limits.movestogo == 0)
|
||||
{
|
||||
// Use extra time with larger increments
|
||||
double optExtra = scaledInc < 500 ? 1.0 : 1.13;
|
||||
if (ply - originalPly < 2)
|
||||
optExtra *= 0.95;
|
||||
// Extra time according to timeLeft
|
||||
if (originalTimeAdjust < 0)
|
||||
originalTimeAdjust = 0.3285 * std::log10(timeLeft) - 0.4830;
|
||||
|
||||
// Calculate time constants based on current time left.
|
||||
double logTimeInSec = std::log10(scaledTime / 1000.0);
|
||||
@@ -117,7 +116,8 @@ void TimeManagement::init(
|
||||
|
||||
optScale = std::min(0.0122 + std::pow(ply + 2.95, 0.462) * optConstant,
|
||||
0.213 * limits.time[us] / timeLeft)
|
||||
* optExtra;
|
||||
* originalTimeAdjust;
|
||||
|
||||
maxScale = std::min(6.64, maxConstant + ply / 12.0);
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -36,8 +36,11 @@ struct LimitsType;
|
||||
// the maximum available time, the game move number, and other parameters.
|
||||
class TimeManagement {
|
||||
public:
|
||||
void init(
|
||||
Search::LimitsType& limits, Color us, int ply, const OptionsMap& options, int& originalPly);
|
||||
void init(Search::LimitsType& limits,
|
||||
Color us,
|
||||
int ply,
|
||||
const OptionsMap& options,
|
||||
double& originalTimeAdjust);
|
||||
|
||||
TimePoint optimum() const;
|
||||
TimePoint maximum() const;
|
||||
|
||||
+1
-1
@@ -124,7 +124,7 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const {
|
||||
const uint16_t key16 = uint16_t(key); // Use the low 16 bits as key inside the cluster
|
||||
|
||||
for (int i = 0; i < ClusterSize; ++i)
|
||||
if (tte[i].key16 == key16 || !tte[i].depth8)
|
||||
if (tte[i].key16 == key16)
|
||||
return found = bool(tte[i].depth8), &tte[i];
|
||||
|
||||
// Find an entry to be replaced according to the replacement strategy
|
||||
|
||||
+6
-6
@@ -389,12 +389,12 @@ WinRateParams win_rate_params(const Position& pos) {
|
||||
int material = pos.count<PAWN>() + 3 * pos.count<KNIGHT>() + 3 * pos.count<BISHOP>()
|
||||
+ 5 * pos.count<ROOK>() + 9 * pos.count<QUEEN>();
|
||||
|
||||
// The fitted model only uses data for material counts in [10, 78], and is anchored at count 58.
|
||||
double m = std::clamp(material, 10, 78) / 58.0;
|
||||
// The fitted model only uses data for material counts in [17, 78], and is anchored at count 58.
|
||||
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[] = {-150.77043883, 394.96159472, -321.73403766, 406.15850091};
|
||||
constexpr double bs[] = {62.33245393, -91.02264855, 45.88486850, 51.63461272};
|
||||
constexpr double as[] = {-41.25712052, 121.47473115, -124.46958843, 411.84490997};
|
||||
constexpr double bs[] = {84.92998051, -143.66658718, 80.09988253, 49.80869370};
|
||||
|
||||
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];
|
||||
@@ -435,8 +435,8 @@ std::string UCIEngine::format_score(const Score& s) {
|
||||
// without treatment of mate and similar special scores.
|
||||
int UCIEngine::to_cp(Value v, const Position& pos) {
|
||||
|
||||
// In general, the score can be defined via the the WDL as
|
||||
// (log(1/L - 1) - log(1/W - 1)) / ((log(1/L - 1) + log(1/W - 1))
|
||||
// In general, the score can be defined via the WDL as
|
||||
// (log(1/L - 1) - log(1/W - 1)) / (log(1/L - 1) + log(1/W - 1)).
|
||||
// Based on our win_rate_model, this simply yields v / a.
|
||||
|
||||
auto [a, b] = win_rate_params(pos);
|
||||
|
||||
Reference in New Issue
Block a user