14#ifndef NUMERICAL_PARALLEL_HPP
15#define NUMERICAL_PARALLEL_HPP
20#include <condition_variable>
41#elif defined(__APPLE__)
42# include <sys/sysctl.h>
52 return static_cast<long>(
_getpid());
54 return static_cast<long>(
getpid());
59 inline constexpr std::size_t
no_core {std::numeric_limits<std::size_t>::max()};
70 for (std::size_t c {0}; c <
cores; ++c) {
79 std::size_t
caller)
noexcept
101 const std::string&
cpu_root)
noexcept
104 std::map<std::pair<long, long>, std::size_t> index;
105 std::vector<cpu_set_t>
sets;
112 const long package {std::stol(first_line(topology + "physical_package_id"))};
143 for (std::size_t c {0}; c <
cores.size(); ++c) {
167 std::size_t index)
noexcept
169 if (index <
cores.size()) {
182#if defined(__linux__)
184#elif defined(__APPLE__)
186 std::size_t size {
sizeof(
cores)};
188 return static_cast<std::size_t
>(std::max(
cores, 0));
221 inline constexpr std::chrono::microseconds
spin_time {500};
224 template <
typename Ready>
227 const std::chrono::steady_clock::time_point
until {std::chrono::steady_clock::now() +
spin_time};
228 while (!
ready() && std::chrono::steady_clock::now() <
until) {
229 std::this_thread::yield();
253 std::atomic<std::size_t>
next {0};
262 for (std::size_t c {
next.fetch_add(1)}; c <
chunks; c =
next.fetch_add(1)) {
267 failed[c] = std::current_exception();
270 const std::lock_guard<std::mutex>
held {
lock};
293 std::size_t helpers)
noexcept
302 return rank < helpers;
360 std::atomic<bool>& busy;
362 explicit release(std::atomic<bool>& flag) : busy {flag}
365 release(
const release&) =
delete;
366 release& operator=(
const release&) =
delete;
367 release(release&&) =
delete;
368 release& operator=(release&&) =
delete;
374 const auto job {std::make_shared<pool_job>()};
379 job->chunks = chunks;
380 job->helpers = helpers;
381#if defined(__linux__)
384 job->failed.resize(chunks);
386 const std::lock_guard<std::mutex>
held {
lock_};
390 for (std::size_t
w {0};
w <
bells_.size(); ++
w) {
392#if defined(__linux__)
397 if (
bell.asleep.load()) {
399 bell.rung.notify_one();
403 if (
side !=
nullptr) {
415 std::unique_lock<std::mutex>
held {
job->lock};
421 for (
const std::exception_ptr&
error :
job->failed) {
423 std::rethrow_exception(
error);
440#if defined(__linux__)
444 bells_.push_back(std::make_unique<worker_bell>(core));
454 bells_.push_back(std::make_unique<worker_bell>(core));
467 std::size_t
seen {0};
468#if defined(__linux__)
473#if defined(__linux__)
481 bell.asleep.store(
true);
482 std::unique_lock<std::mutex>
held {
bell.lock};
484 bell.asleep.store(
false);
486 std::shared_ptr<pool_job>
job;
488 const std::lock_guard<std::mutex>
held {
lock_};
492#if defined(__linux__)
498#if defined(__linux__)
506 if (
wanted &&
job->joined.fetch_add(1) <
job->helpers) {
513 std::vector<std::unique_ptr<worker_bell>>
bells_;
518#if defined(__linux__)
527 static std::atomic<std::size_t>
cap {0};
558 ? std::numeric_limits<std::size_t>::max()
579 template <
typename Body>
586 const auto runner {[](
const void*
erased, std::size_t begin, std::size_t end) {
588 for (std::size_t i {begin}; i < end; ++i) {
597 const std::size_t chunks {std::max<std::size_t>(1, std::min(count,
threads * 4))};
598 const std::size_t chunk {((count + chunks) - 1) / chunks};
616 template <
typename Body,
625 const auto runner {[](
const void*
erased, std::size_t begin, std::size_t end) {
627 for (std::size_t i {begin}; i < end; ++i) {
633 const std::size_t chunks {std::max<std::size_t>(1, std::min(count,
threads * 4))};
634 const std::size_t chunk {((count + chunks) - 1) / chunks};
644 inline constexpr std::size_t
zero_chunk {std::size_t {1} << 12};
653 template <
typename T>
656#if defined(__linux__)
657 constexpr std::size_t
huge_page {std::size_t {1} << 21U};
658 constexpr std::size_t
huge_least {std::size_t {1} << 24U};
681 template <
typename T>
686 std::fill(
out,
out + count,
T {});
691 const std::size_t begin {chunk *
piece};
692 std::fill(
out + begin,
out + std::min(count, begin +
piece),
T {});
702 template <
typename T>
708 std::copy(
in,
in + count,
out);
713 const std::size_t begin {chunk *
piece};
714 std::copy(
in + begin,
in + std::min(count, begin +
piece),
out + begin);
728 detail::thread_cap().store(threads);
734 const std::size_t cap {detail::thread_cap().load()};
735 const std::size_t hardware {detail::hardware_threads()};
736 return cap == 0 ? hardware : std::min(cap, hardware);
The threads every kernel shares, started once: a parallel_for costs a wake-up, not a thread start.
std::mutex lock_
Guards current_ and generation_.
thread_pool & operator=(thread_pool &&)=delete
thread_pool & operator=(const thread_pool &)=delete
std::shared_ptr< pool_job > current_
The latest run.
static thread_pool & shared()
The pool, started on first use with a worker per hardware thread beside the caller.
thread_pool(thread_pool &&)=delete
std::size_t workers_
The threads started.
thread_pool(const thread_pool &)=delete
std::atomic< bool > busy_
Whether a run holds the pool.
void start()
Starts the workers (after construction: each uses the pool), worker_cores: on Linux one per core,...
bool try_run(chunk_runner run, const void *body, std::size_t count, std::size_t chunk, std::size_t chunks, std::size_t helpers, side_runner side=nullptr, const void *side_body=nullptr)
Runs chunks chunks of chunk indices up to count on the caller and at most helpers workers,...
std::vector< std::unique_ptr< worker_bell > > bells_
Each worker's, set before it starts.
void serve(worker_bell &bell)
A worker on core: waits for a run (polling a while, then asleep), works on it unless it sits it out,...
long owner_
The process whose threads they are.
std::atomic< std::size_t > generation_
Counts the runs (polled without the lock).
constexpr std::size_t no_core
No core: a worker free to run anywhere, or a caller on a processor the pool does not know.
constexpr std::size_t zero_chunk
The elements one index of parallel_zero writes: eight 4 KiB pages of double.
void parallel_zero(T *out, std::size_t count)
Sets the count elements at out to zero, split across the threads the work pays for: a fresh result's ...
void parallel_for_beside(std::size_t count, std::size_t work_per_index, const Body &body, const Side &side)
Runs side on the caller while the pool's workers run body(i) for every i in [0, count),...
constexpr bool sits_out(std::size_t worker, std::size_t caller) noexcept
Whether the worker pinned to core worker sits out a run whose caller is on core caller: one thread pe...
constexpr std::size_t zero_work
The work of zeroing one element, in multiply-adds: a second thread from 1 MiB of double....
std::vector< std::size_t > worker_cores(std::size_t cores, std::size_t threads)
The core of each worker the pool starts beside its callers, from the cores known (0: none) and its th...
constexpr std::size_t vector_speedup
The multiply-adds a vectorised inner loop does in the time of one in work_per_thread's dependent chai...
constexpr std::size_t pool_threads(std::size_t logical, std::size_t physical) noexcept
The pool's threads from the logical hardware threads and the physical cores (0: unknown): the cores w...
constexpr bool wants(std::size_t core, std::size_t caller, std::size_t helpers) noexcept
Whether the worker on core joins a run of helpers workers whose caller is on caller: by rank among th...
void(*)(const void *side) side_runner
A task the caller of a run does before it takes chunks (parallel_for_beside), type-erased.
constexpr std::size_t work_per_thread
The work one thread must have to pay for starting it: about 60 µs of multiply-adds,...
void parallel_copy(const T *in, std::size_t count, T *out)
Copies the count elements at in to out, split across the threads the work pays for,...
constexpr std::size_t elementwise_chunk
The elements one task of an element-wise operation takes, a quarter of a thread's work: a tensor of a...
constexpr std::chrono::microseconds spin_time
How long a thread polls before it sleeps: kernels run in quick succession (a matrix product packs,...
std::size_t threads_for(std::size_t count, std::size_t work_per_index, std::size_t hardware)
How many threads count indices of work_per_index each pay for: one per work_per_thread,...
void(*)(const void *body, std::size_t begin, std::size_t end) chunk_runner
A chunk of a parallel_for, type-erased: runs body(i) for i in [begin, end).
void spin_until(const Ready &ready)
Polls ready, yielding the core between polls, until it holds or spin_time has passed.
void parallel_for(std::size_t count, std::size_t work_per_index, const Body &body)
Runs body(i) for every i in [0, count).
long current_process() noexcept
This process's identifier: a pool started before a fork belongs to the parent, whose threads the chil...
std::atomic< std::size_t > & thread_cap() noexcept
The most threads a parallel_for uses (0: as many as the pool has), set by numerical::set_max_threads.
std::size_t physical_cores() noexcept
The physical cores this process may run on, or 0 where the system does not say. Linux: core_sets; mac...
std::size_t hardware_threads() noexcept
The threads the pool runs, the caller's included (at least 1), known without starting the pool: one p...
constexpr std::size_t fault_chunk(std::size_t count) noexcept
The elements one index of parallel_zero or parallel_copy takes of count: zero_chunk,...
The NumericAL public API.
std::size_t max_threads()
The threads a kernel may use: the cap if one is set, else the hardware threads (starting none).
void set_max_threads(std::size_t threads) noexcept
Caps the threads a kernel uses at threads, the caller's included (1: none beside it,...
T min(const tensor< T > &t)
The smallest element (a NaN, if there is one).
One parallel_for handed to the pool: its chunks, the next one to take, and how many are done....
chunk_runner run
The chunk runner.
std::atomic< std::size_t > finished
The chunks done.
std::size_t chunk
Indices per chunk.
std::size_t count
The number of iterations.
std::mutex lock
Orders the last chunk's signal against the caller's sleep.
std::size_t core
The caller's core: its worker sits it out (sits_out).
std::atomic< std::size_t > joined
The workers that asked to.
std::size_t helpers
The workers that may join it beside the caller.
std::size_t chunks
Chunks.
std::atomic< std::size_t > next
The next chunk to take.
void work() noexcept
Takes chunks until none is left, keeping the exception each throws.
std::condition_variable done
Signalled when the last chunk finishes.
const void * body
The body it is handed.
std::vector< std::exception_ptr > failed
The exception of each chunk, if it threw.
A worker's own wake-up: its core, whether it sleeps, and what wakes it.
const std::size_t core
The core it is pinned to, or no_core.
std::condition_variable rung
Signalled when a run that wants it starts.
std::atomic< bool > asleep
Set before it checks for a run and sleeps.
std::mutex lock
Orders a ring against its sleep.
worker_bell(std::size_t on)