X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;ds=sidebyside;f=src%2Fthread.h;h=d6a48eca73d14937f19212d462d9a120398d21d0;hb=HEAD;hp=0b112de9b0f14540d414c506f61998053ad0c72e;hpb=7eaea3848c9e8a388c0b79cee6fba6bf3cd32108;p=stockfish diff --git a/src/thread.h b/src/thread.h index 0b112de9..43e2e142 100644 --- a/src/thread.h +++ b/src/thread.h @@ -1,8 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2004-2024 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 @@ -22,86 +20,159 @@ #define THREAD_H_INCLUDED #include -#include #include +#include +#include +#include +#include #include -#include #include -#include "material.h" -#include "movepick.h" -#include "pawns.h" +#include "numa.h" #include "position.h" #include "search.h" -#include "thread_win32.h" - - -/// Thread struct keeps together all the thread-related stuff. We also use -/// per-thread pawn and material hash tables so that once we get a pointer to an -/// entry its life time is unlimited and we don't have to care about someone -/// changing the entry under our feet. - -class Thread { - - std::thread nativeThread; - Mutex mutex; - ConditionVariable sleepCondition; - bool exit, searching; - -public: - Thread(); - virtual ~Thread(); - virtual void search(); - void idle_loop(); - void start_searching(bool resume = false); - void wait_for_search_finished(); - void wait(std::atomic_bool& b); - - Pawns::Table pawnsTable; - Material::Table materialTable; - Endgames endgames; - size_t idx, PVIdx; - int maxPly, callsCnt; - - Position rootPos; - Search::RootMoves rootMoves; - Depth rootDepth; - HistoryStats history; - MoveStats counterMoves; - Depth completedDepth; - std::atomic_bool resetCalls; +#include "thread_win32_osx.h" + +namespace Stockfish { + + +class OptionsMap; +using Value = int; + +// Sometimes we don't want to actually bind the threads, but the recipient still +// needs to think it runs on *some* NUMA node, such that it can access structures +// that rely on NUMA node knowledge. This class encapsulates this optional process +// such that the recipient does not need to know whether the binding happened or not. +class OptionalThreadToNumaNodeBinder { + public: + OptionalThreadToNumaNodeBinder(NumaIndex n) : + numaConfig(nullptr), + numaId(n) {} + + OptionalThreadToNumaNodeBinder(const NumaConfig& cfg, NumaIndex n) : + numaConfig(&cfg), + numaId(n) {} + + NumaReplicatedAccessToken operator()() const { + if (numaConfig != nullptr) + return numaConfig->bind_current_thread_to_numa_node(numaId); + else + return NumaReplicatedAccessToken(numaId); + } + + private: + const NumaConfig* numaConfig; + NumaIndex numaId; }; - -/// MainThread is a derived class with a specific overload for the main thread - -struct MainThread : public Thread { - virtual void search(); - - bool easyMovePlayed, failedLow; - double bestMoveChanges; - Value previousScore; +// Abstraction of a thread. It contains a pointer to the worker and a native thread. +// After construction, the native thread is started with idle_loop() +// waiting for a signal to start searching. +// When the signal is received, the thread starts searching and when +// the search is finished, it goes back to idle_loop() waiting for a new signal. +class Thread { + public: + Thread(Search::SharedState&, + std::unique_ptr, + size_t, + OptionalThreadToNumaNodeBinder); + virtual ~Thread(); + + void idle_loop(); + void start_searching(); + void clear_worker(); + void run_custom_job(std::function f); + + void ensure_network_replicated(); + + // Thread has been slightly altered to allow running custom jobs, so + // this name is no longer correct. However, this class (and ThreadPool) + // require further work to make them properly generic while maintaining + // appropriate specificity regarding search, from the point of view of an + // outside user, so renaming of this function is left for whenever that happens. + void wait_for_search_finished(); + size_t id() const { return idx; } + + std::unique_ptr worker; + std::function jobFunc; + + private: + std::mutex mutex; + std::condition_variable cv; + size_t idx, nthreads; + bool exit = false, searching = true; // Set before starting std::thread + NativeThread stdThread; + NumaReplicatedAccessToken numaAccessToken; }; -/// ThreadPool struct handles all the threads-related stuff like init, starting, -/// parking and, most importantly, launching a thread. All the access to threads -/// data is done through this class. - -struct ThreadPool : public std::vector { - - void init(); // No constructor and destructor, threads rely on globals that should - void exit(); // be initialized and valid during the whole thread lifetime. - - MainThread* main() { return static_cast(at(0)); } - void start_thinking(const Position&, StateListPtr&, const Search::LimitsType&); - void read_uci_options(); - int64_t nodes_searched(); - -private: - StateListPtr setupStates; +// ThreadPool struct handles all the threads-related stuff like init, starting, +// parking and, most importantly, launching a thread. All the access to threads +// is done through this class. +class ThreadPool { + public: + ThreadPool() {} + + ~ThreadPool() { + // destroy any existing thread(s) + if (threads.size() > 0) + { + main_thread()->wait_for_search_finished(); + + threads.clear(); + } + } + + ThreadPool(const ThreadPool&) = delete; + ThreadPool(ThreadPool&&) = delete; + + ThreadPool& operator=(const ThreadPool&) = delete; + ThreadPool& operator=(ThreadPool&&) = delete; + + void start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType); + void run_on_thread(size_t threadId, std::function f); + void wait_on_thread(size_t threadId); + size_t num_threads() const; + void clear(); + void set(const NumaConfig& numaConfig, + Search::SharedState, + const Search::SearchManager::UpdateContext&); + + Search::SearchManager* main_manager(); + Thread* main_thread() const { return threads.front().get(); } + uint64_t nodes_searched() const; + uint64_t tb_hits() const; + Thread* get_best_thread() const; + void start_searching(); + void wait_for_search_finished() const; + + std::vector get_bound_thread_count_by_numa_node() const; + + void ensure_network_replicated(); + + std::atomic_bool stop, abortedSearch, increaseDepth; + + auto cbegin() const noexcept { return threads.cbegin(); } + auto begin() noexcept { return threads.begin(); } + auto end() noexcept { return threads.end(); } + auto cend() const noexcept { return threads.cend(); } + auto size() const noexcept { return threads.size(); } + auto empty() const noexcept { return threads.empty(); } + + private: + StateListPtr setupStates; + std::vector> threads; + std::vector boundThreadToNumaNode; + + uint64_t accumulate(std::atomic Search::Worker::*member) const { + + uint64_t sum = 0; + for (auto&& th : threads) + sum += (th->worker.get()->*member).load(std::memory_order_relaxed); + return sum; + } }; -extern ThreadPool Threads; +} // namespace Stockfish -#endif // #ifndef THREAD_H_INCLUDED +#endif // #ifndef THREAD_H_INCLUDED