mirror of
https://github.com/HChaZZY/Stockfish.git
synced 2025-12-25 03:26:24 +08:00
550 lines
15 KiB
C++
550 lines
15 KiB
C++
/*
|
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
|
|
|
Stockfish is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Stockfish is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifndef MISC_H_INCLUDED
|
|
#define MISC_H_INCLUDED
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <chrono>
|
|
#include <functional>
|
|
#include <mutex>
|
|
#include <ostream>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <utility>
|
|
#include <cmath>
|
|
#include <cctype>
|
|
#include <sstream>
|
|
#include <deque>
|
|
|
|
#include "types.h"
|
|
|
|
const std::string engine_info(bool to_uci = false);
|
|
const std::string compiler_info();
|
|
void prefetch(void* addr);
|
|
void start_logger(const std::string& fname);
|
|
void* std_aligned_alloc(size_t alignment, size_t size);
|
|
void std_aligned_free(void* ptr);
|
|
void* aligned_large_pages_alloc(size_t size); // memory aligned by page size, min alignment: 4096 bytes
|
|
void aligned_large_pages_free(void* mem); // nop if mem == nullptr
|
|
|
|
void dbg_hit_on(bool b);
|
|
void dbg_hit_on(bool c, bool b);
|
|
void dbg_mean_of(int v);
|
|
void dbg_print();
|
|
|
|
typedef std::chrono::milliseconds::rep TimePoint; // A value in milliseconds
|
|
static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits");
|
|
inline TimePoint now() {
|
|
return std::chrono::duration_cast<std::chrono::milliseconds>
|
|
(std::chrono::steady_clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
template<class Entry, int Size>
|
|
struct HashTable {
|
|
Entry* operator[](Key key) { return &table[(uint32_t)key & (Size - 1)]; }
|
|
|
|
private:
|
|
std::vector<Entry> table = std::vector<Entry>(Size); // Allocate on the heap
|
|
};
|
|
|
|
|
|
enum SyncCout { IO_LOCK, IO_UNLOCK };
|
|
std::ostream& operator<<(std::ostream&, SyncCout);
|
|
|
|
#define sync_cout std::cout << IO_LOCK
|
|
#define sync_endl std::endl << IO_UNLOCK
|
|
|
|
// This logger allows printing many parts in a region atomically
|
|
// but doesn't block the threads trying to append to other regions.
|
|
// Instead if some region tries to pring while other region holds
|
|
// the lock the messages are queued to be printed as soon as the
|
|
// current region releases the lock.
|
|
struct SynchronizedRegionLogger
|
|
{
|
|
private:
|
|
using RegionId = std::uint64_t;
|
|
|
|
struct RegionLock
|
|
{
|
|
RegionLock(SynchronizedRegionLogger& log, RegionId id) :
|
|
logger(&log), region_id(id), is_held(true)
|
|
{
|
|
}
|
|
|
|
RegionLock(const RegionLock&) = delete;
|
|
RegionLock& operator=(const RegionLock&) = delete;
|
|
|
|
RegionLock(RegionLock&& other) :
|
|
logger(other.logger), region_id(other.region_id), is_held(other.is_held)
|
|
{
|
|
other.logger = nullptr;
|
|
other.is_held = false;
|
|
}
|
|
|
|
RegionLock& operator=(RegionLock&& other) {
|
|
if (is_held && logger != nullptr)
|
|
{
|
|
logger->release_region(region_id);
|
|
}
|
|
|
|
logger = other.logger;
|
|
region_id = other.region_id;
|
|
is_held = other.is_held;
|
|
|
|
other.is_held = false;
|
|
|
|
return *this;
|
|
}
|
|
|
|
~RegionLock() { unlock(); }
|
|
|
|
void unlock() {
|
|
if (is_held) {
|
|
is_held = false;
|
|
|
|
if (logger != nullptr)
|
|
logger->release_region(region_id);
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
RegionLock& operator << (const T& value) {
|
|
if (logger != nullptr)
|
|
logger->write(region_id, value);
|
|
|
|
return *this;
|
|
}
|
|
|
|
private:
|
|
SynchronizedRegionLogger* logger;
|
|
RegionId region_id;
|
|
bool is_held;
|
|
};
|
|
|
|
struct Region
|
|
{
|
|
Region(RegionId rid) : id(rid), is_held(true) {}
|
|
|
|
std::vector<std::string> pending_parts;
|
|
RegionId id;
|
|
bool is_held;
|
|
};
|
|
|
|
RegionId init_next_region()
|
|
{
|
|
static RegionId next_id = 0;
|
|
|
|
std::lock_guard lock(mutex);
|
|
|
|
const auto id = next_id++;
|
|
regions.emplace_back(id);
|
|
|
|
return id;
|
|
}
|
|
|
|
template <typename T>
|
|
void write(RegionId id, const T& value) {
|
|
std::lock_guard lock(mutex);
|
|
|
|
if (regions.empty())
|
|
return;
|
|
|
|
if (id == regions.front().id) {
|
|
// We can just directly print to the output because
|
|
// we are at the front of the region queue.
|
|
out << value;
|
|
} else {
|
|
// We have to schedule the print until previous regions are
|
|
// processed
|
|
auto* region = find_region_nolock(id);
|
|
if (region == nullptr)
|
|
return;
|
|
|
|
std::stringstream ss;
|
|
ss << value;
|
|
region->pending_parts.emplace_back(std::move(ss).str());
|
|
}
|
|
}
|
|
|
|
std::ostream& out;
|
|
|
|
std::deque<Region> regions;
|
|
|
|
std::mutex mutex;
|
|
|
|
Region* find_region_nolock(RegionId id) {
|
|
// Linear search because the amount of concurrent regions should be small.
|
|
auto it = std::find_if(
|
|
regions.begin(),
|
|
regions.end(),
|
|
[id](const Region& r) { return r.id == id; });
|
|
|
|
if (it == regions.end())
|
|
return nullptr;
|
|
else
|
|
return &*it;
|
|
}
|
|
|
|
void release_region(RegionId id) {
|
|
std::lock_guard lock(mutex);
|
|
|
|
auto* region = find_region_nolock(id);
|
|
if (region == nullptr)
|
|
return;
|
|
|
|
region->is_held = false;
|
|
|
|
process_backlog_nolock();
|
|
}
|
|
|
|
void process_backlog_nolock()
|
|
{
|
|
while(!regions.empty()) {
|
|
auto& region = regions.front();
|
|
|
|
for(auto& part : region.pending_parts) {
|
|
out << part;
|
|
}
|
|
|
|
// If the region is still held then we don't
|
|
// want to start printing stuff from the next region.
|
|
if (region.is_held)
|
|
break;
|
|
|
|
regions.pop_front();
|
|
}
|
|
}
|
|
|
|
public:
|
|
|
|
SynchronizedRegionLogger(std::ostream& s) :
|
|
out(s)
|
|
{
|
|
}
|
|
|
|
[[nodiscard]] RegionLock new_region() {
|
|
const auto id = init_next_region();
|
|
return RegionLock(*this, id);
|
|
}
|
|
|
|
};
|
|
|
|
extern SynchronizedRegionLogger sync_region_cout;
|
|
|
|
|
|
/// xorshift64star Pseudo-Random Number Generator
|
|
/// This class is based on original code written and dedicated
|
|
/// to the public domain by Sebastiano Vigna (2014).
|
|
/// It has the following characteristics:
|
|
///
|
|
/// - Outputs 64-bit numbers
|
|
/// - Passes Dieharder and SmallCrush test batteries
|
|
/// - Does not require warm-up, no zeroland to escape
|
|
/// - Internal state is a single 64-bit integer
|
|
/// - Period is 2^64 - 1
|
|
/// - Speed: 1.60 ns/call (Core i7 @3.40GHz)
|
|
///
|
|
/// For further analysis see
|
|
/// <http://vigna.di.unimi.it/ftp/papers/xorshift.pdf>
|
|
|
|
static uint64_t string_hash(const std::string& str)
|
|
{
|
|
uint64_t h = 525201411107845655ull;
|
|
|
|
for (auto c : str) {
|
|
h ^= static_cast<uint64_t>(c);
|
|
h *= 0x5bd1e9955bd1e995ull;
|
|
h ^= h >> 47;
|
|
}
|
|
|
|
return h;
|
|
}
|
|
|
|
class PRNG {
|
|
|
|
uint64_t s;
|
|
|
|
uint64_t rand64() {
|
|
|
|
s ^= s >> 12, s ^= s << 25, s ^= s >> 27;
|
|
return s * 2685821657736338717LL;
|
|
}
|
|
|
|
public:
|
|
PRNG() { set_seed_from_time(); }
|
|
PRNG(uint64_t seed) : s(seed) { assert(seed); }
|
|
PRNG(const std::string& seed) { set_seed(seed); }
|
|
|
|
template<typename T> T rand() { return T(rand64()); }
|
|
|
|
/// Special generator used to fast init magic numbers.
|
|
/// Output values only have 1/8th of their bits set on average.
|
|
template<typename T> T sparse_rand()
|
|
{ return T(rand64() & rand64() & rand64()); }
|
|
// Returns a random number from 0 to n-1. (Not uniform distribution, but this is enough in reality)
|
|
uint64_t rand(uint64_t n) { return rand<uint64_t>() % n; }
|
|
|
|
// Return the random seed used internally.
|
|
uint64_t get_seed() const { return s; }
|
|
|
|
void set_seed(uint64_t seed) { s = seed; }
|
|
|
|
uint64_t next_random_seed()
|
|
{
|
|
uint64_t seed = 0;
|
|
for(int i = 0; i < 64; ++i)
|
|
{
|
|
const auto off = rand64() % 64;
|
|
seed |= (rand64() & (uint64_t(1) << off)) >> off;
|
|
seed <<= 1;
|
|
}
|
|
return seed;
|
|
}
|
|
|
|
void set_seed_from_time()
|
|
{
|
|
set_seed(std::chrono::system_clock::now().time_since_epoch().count());
|
|
}
|
|
|
|
void set_seed(const std::string& str)
|
|
{
|
|
if (str.empty())
|
|
{
|
|
set_seed_from_time();
|
|
}
|
|
else if (std::all_of(str.begin(), str.end(), [](char c) { return std::isdigit(c);} )) {
|
|
set_seed(std::stoull(str));
|
|
}
|
|
else
|
|
{
|
|
set_seed(string_hash(str));
|
|
}
|
|
}
|
|
};
|
|
|
|
// Display a random seed. (For debugging)
|
|
inline std::ostream& operator<<(std::ostream& os, PRNG& prng)
|
|
{
|
|
os << "PRNG::seed = " << std::hex << prng.get_seed() << std::dec;
|
|
return os;
|
|
}
|
|
|
|
inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
|
|
#if defined(__GNUC__) && defined(IS_64BIT)
|
|
__extension__ typedef unsigned __int128 uint128;
|
|
return ((uint128)a * (uint128)b) >> 64;
|
|
#else
|
|
uint64_t aL = (uint32_t)a, aH = a >> 32;
|
|
uint64_t bL = (uint32_t)b, bH = b >> 32;
|
|
uint64_t c1 = (aL * bL) >> 32;
|
|
uint64_t c2 = aH * bL + c1;
|
|
uint64_t c3 = aL * bH + (uint32_t)c2;
|
|
return aH * bH + (c2 >> 32) + (c3 >> 32);
|
|
#endif
|
|
}
|
|
|
|
/// Under Windows it is not possible for a process to run on more than one
|
|
/// logical processor group. This usually means to be limited to use max 64
|
|
/// cores. To overcome this, some special platform specific API should be
|
|
/// called to set group affinity for each thread. Original code from Texel by
|
|
/// Peter Österlund.
|
|
|
|
namespace WinProcGroup {
|
|
void bindThisThread(size_t idx);
|
|
}
|
|
// sleep for the specified number of milliseconds.
|
|
extern void sleep(int ms);
|
|
|
|
// Returns a string that represents the current time. (Used for log output when learning evaluation function)
|
|
std::string now_string();
|
|
|
|
// When compiled with gcc/clang such as msys2, Windows Subsystem for Linux,
|
|
// In C++ std::ifstream, ::read() is a wrapper for that because it is not possible to read and write files larger than 2GB in one shot.
|
|
//
|
|
// callback_func of the argument of read_file_to_memory() uses the file size as an argument when the file can be opened
|
|
// It will be called back, so if you allocate a buffer and pass a function that returns the first pointer, it will be read there.
|
|
// These functions return non-zero on error, such as when the file cannot be found.
|
|
//
|
|
// Also, if the buffer cannot be allocated in the callback function or if the file size is different from the expected file size,
|
|
// Return nullptr. At this time, read_file_to_memory() interrupts reading and returns with an error.
|
|
|
|
std::uint64_t get_file_size(std::fstream& fs);
|
|
int read_file_to_memory(std::string filename, std::function<void* (uint64_t)> callback_func);
|
|
int write_memory_to_file(std::string filename, void* ptr, uint64_t size);
|
|
|
|
// --------------------
|
|
// async version of PRNG
|
|
// --------------------
|
|
|
|
// async version of PRNG
|
|
struct AsyncPRNG
|
|
{
|
|
AsyncPRNG() : prng() { }
|
|
AsyncPRNG(uint64_t seed) : prng(seed) { assert(seed); }
|
|
AsyncPRNG(const std::string& seed) : prng(seed) { }
|
|
// [ASYNC] Extract one random number.
|
|
template<typename T> T rand() {
|
|
std::unique_lock<std::mutex> lk(mutex);
|
|
return prng.rand<T>();
|
|
}
|
|
|
|
// [ASYNC] Returns a random number from 0 to n-1. (Not uniform distribution, but this is enough in reality)
|
|
uint64_t rand(uint64_t n) {
|
|
std::unique_lock<std::mutex> lk(mutex);
|
|
return prng.rand(n);
|
|
}
|
|
|
|
// Return the random seed used internally.
|
|
uint64_t get_seed() const { return prng.get_seed(); }
|
|
|
|
protected:
|
|
std::mutex mutex;
|
|
PRNG prng;
|
|
};
|
|
|
|
// Display a random seed. (For debugging)
|
|
inline std::ostream& operator<<(std::ostream& os, AsyncPRNG& prng)
|
|
{
|
|
os << "AsyncPRNG::seed = " << std::hex << prng.get_seed() << std::dec;
|
|
return os;
|
|
}
|
|
|
|
// --------------------
|
|
// Math
|
|
// --------------------
|
|
|
|
// Mathematical function used for progress calculation and learning
|
|
namespace Math {
|
|
inline double sigmoid(double x)
|
|
{
|
|
return 1.0 / (1.0 + std::exp(-x));
|
|
}
|
|
|
|
inline double dsigmoid(double x)
|
|
{
|
|
// Sigmoid function
|
|
// f(x) = 1/(1+exp(-x))
|
|
// the first derivative is
|
|
// f'(x) = df/dx = f(x)・{ 1-f(x)}
|
|
// becomes
|
|
|
|
return sigmoid(x) * (1.0 - sigmoid(x));
|
|
}
|
|
|
|
// Clip v so that it fits between [lo,hi].
|
|
// * In Stockfish, this function is written in bitboard.h.
|
|
template<class T> constexpr const T& clamp(const T& v, const T& lo, const T& hi) {
|
|
return v < lo ? lo : v > hi ? hi : v;
|
|
}
|
|
}
|
|
|
|
namespace Algo {
|
|
// Fisher-Yates
|
|
template <typename Rng, typename T>
|
|
void shuffle(std::vector<T>& buf, Rng&& prng)
|
|
{
|
|
const auto size = buf.size();
|
|
for (uint64_t i = 0; i < size; ++i)
|
|
std::swap(buf[i], buf[prng.rand(size - i) + i]);
|
|
}
|
|
|
|
// split the string
|
|
inline std::vector<std::string> split(const std::string& input, char delimiter) {
|
|
std::istringstream stream(input);
|
|
std::string field;
|
|
std::vector<std::string> fields;
|
|
|
|
while (std::getline(stream, field, delimiter)) {
|
|
fields.push_back(field);
|
|
}
|
|
|
|
return fields;
|
|
}
|
|
}
|
|
|
|
// --------------------
|
|
// Path
|
|
// --------------------
|
|
|
|
// Something like Path class in C#. File name manipulation.
|
|
// Match with the C# method name.
|
|
struct Path
|
|
{
|
|
// Combine the path name and file name and return it.
|
|
// If the folder name is not an empty string, append it if there is no'/' or'\\' at the end.
|
|
static std::string combine(const std::string& folder, const std::string& filename)
|
|
{
|
|
if (folder.length() >= 1 && *folder.rbegin() != '/' && *folder.rbegin() != '\\')
|
|
return folder + "/" + filename;
|
|
|
|
return folder + filename;
|
|
}
|
|
|
|
// Get the file name part (excluding the folder name) from the full path expression.
|
|
static std::string get_file_name(const std::string& path)
|
|
{
|
|
// I don't know which "\" or "/" is used.
|
|
auto path_index1 = path.find_last_of("\\") + 1;
|
|
auto path_index2 = path.find_last_of("/") + 1;
|
|
auto path_index = std::max(path_index1, path_index2);
|
|
|
|
return path.substr(path_index);
|
|
}
|
|
};
|
|
|
|
// It is ignored when new even though alignas is specified & because it is ignored when the STL container allocates memory,
|
|
// A custom allocator used for that.
|
|
template <typename T>
|
|
class AlignedAllocator {
|
|
public:
|
|
using value_type = T;
|
|
|
|
AlignedAllocator() {}
|
|
AlignedAllocator(const AlignedAllocator&) {}
|
|
AlignedAllocator(AlignedAllocator&&) {}
|
|
|
|
template <typename U> AlignedAllocator(const AlignedAllocator<U>&) {}
|
|
|
|
T* allocate(std::size_t n) { return (T*)std_aligned_alloc(alignof(T), n * sizeof(T)); }
|
|
void deallocate(T* p, std::size_t ) { std_aligned_free(p); }
|
|
};
|
|
|
|
// --------------------
|
|
// Dependency Wrapper
|
|
// --------------------
|
|
|
|
namespace Dependency
|
|
{
|
|
// In the Linux environment, if you getline() the text file is'\r\n'
|
|
// Since'\r' remains at the end, write a wrapper to remove this'\r'.
|
|
// So when calling getline() on fstream,
|
|
// just write getline() instead of std::getline() and use this function.
|
|
extern bool getline(std::ifstream& fs, std::string& s);
|
|
}
|
|
|
|
namespace CommandLine {
|
|
void init(int argc, char* argv[]);
|
|
|
|
extern std::string binaryDirectory; // path of the executable directory
|
|
extern std::string workingDirectory; // path of the working directory
|
|
}
|
|
|
|
#endif // #ifndef MISC_H_INCLUDED
|