NumericAL
A header-only C++20 lexer built on REAL
Loading...
Searching...
No Matches
parallel.hpp
Go to the documentation of this file.
1
14#ifndef NUMERICAL_PARALLEL_HPP
15#define NUMERICAL_PARALLEL_HPP
16
17#include <algorithm>
18#include <atomic>
19#include <chrono>
20#include <condition_variable>
21#include <cstddef>
22#include <exception>
23#include <limits>
24#include <memory>
25#include <mutex>
26#include <span>
27#include <thread>
28#include <vector>
29
30#if defined(_WIN32)
31# include <process.h>
32#else
33# include <unistd.h>
34#endif
35#if defined(__linux__)
36# include <fstream>
37# include <map>
38# include <string>
39# include <utility>
40# include <sched.h>
41#elif defined(__APPLE__)
42# include <sys/sysctl.h>
43#endif
44
45namespace numerical::detail {
46
50 {
51#if defined(_WIN32)
52 return static_cast<long>(_getpid());
53#else
54 return static_cast<long>(getpid());
55#endif
56 }
57
59 inline constexpr std::size_t no_core {std::numeric_limits<std::size_t>::max()};
60
66 [[nodiscard]] inline std::vector<std::size_t> worker_cores(std::size_t cores,
67 std::size_t threads)
68 {
69 std::vector<std::size_t> out(cores != 0 ? cores : threads - 1, no_core);
70 for (std::size_t c {0}; c < cores; ++c) {
71 out[c] = c;
72 }
73 return out;
74 }
75
78 [[nodiscard]] constexpr bool sits_out(std::size_t worker,
79 std::size_t caller) noexcept
80 {
81 return worker != no_core && worker == caller;
82 }
83
84#if defined(__linux__)
86 [[nodiscard]] inline std::string first_line(const std::string& path)
87 {
88 std::ifstream file {path};
89 std::string line;
90 std::getline(file, line);
91 return line;
92 }
93
100 [[nodiscard]] inline std::vector<cpu_set_t> core_sets_from(const cpu_set_t& allowed,
101 const std::string& cpu_root) noexcept
102 {
103 try {
104 std::map<std::pair<long, long>, std::size_t> index; // (package, core) → its set
105 std::vector<cpu_set_t> sets;
106 for (int cpu {0}; cpu < CPU_SETSIZE; ++cpu) {
107 if (CPU_ISSET(cpu, &allowed) == 0) { // NOLINT(hicpp-signed-bitwise): the system's macro
108 continue;
109 }
110 const std::string topology {cpu_root + "cpu" + std::to_string(cpu) + "/topology/"};
111 const long core {std::stol(first_line(topology + "core_id"))};
112 const long package {std::stol(first_line(topology + "physical_package_id"))};
113 const auto [where, added] {index.try_emplace({package, core}, sets.size())};
114 if (added) {
115 sets.emplace_back();
116 CPU_ZERO(&sets.back()); // NOLINT(hicpp-signed-bitwise): the system's macro
117 }
118 CPU_SET(cpu, &sets[where->second]); // NOLINT(hicpp-signed-bitwise): the system's macro
119 }
120 return sets;
121 }
122 catch (...) {
123 return {};
124 }
125 }
126
131 [[nodiscard]] inline std::vector<cpu_set_t> core_sets() noexcept
132 {
134 CPU_ZERO(&allowed); // NOLINT(hicpp-signed-bitwise): the system's macro
135 static_cast<void>(sched_getaffinity(0, sizeof(allowed), &allowed)); // on failure the set stays empty
136 return core_sets_from(allowed, "/sys/devices/system/cpu/");
137 }
138
140 [[nodiscard]] inline std::vector<std::size_t> processor_cores(const std::vector<cpu_set_t>& cores)
141 {
142 std::vector<std::size_t> table(CPU_SETSIZE, no_core);
143 for (std::size_t c {0}; c < cores.size(); ++c) {
144 for (std::size_t cpu {0}; cpu < table.size(); ++cpu) {
145 if (CPU_ISSET(cpu, &cores[c]) != 0) { // NOLINT(hicpp-signed-bitwise): the system's macro
146 table[cpu] = c;
147 }
148 }
149 }
150 return table;
151 }
152
155 [[nodiscard]] inline std::size_t core_of(const std::vector<std::size_t>& table,
156 int cpu) noexcept
157 {
158 // Through the subspan from `cpu`: GCC 15 at -O3 reads `table[cpu]` behind an index check (a conditional or an
159 // early return) before the check, and refuses the subscript past the end (-Werror=array-bounds).
160 const std::span<const std::size_t> rest {std::span {table}.subspan(cpu < 0 ? table.size() : std::min(table.size(), static_cast<std::size_t>(cpu)))};
161 return rest.empty() ? no_core : rest.front();
162 }
163
166 inline void pin_to_core(const std::vector<cpu_set_t>& cores,
167 std::size_t index) noexcept
168 {
169 if (index < cores.size()) {
170 (void)sched_setaffinity(0, sizeof(cpu_set_t), &cores[index]);
171 }
172 }
173
174#endif
175
180 [[nodiscard]] inline std::size_t physical_cores() noexcept
181 {
182#if defined(__linux__)
183 return core_sets().size();
184#elif defined(__APPLE__)
185 int cores {0};
186 std::size_t size {sizeof(cores)};
187 (void)sysctlbyname("hw.physicalcpu", &cores, &size, nullptr, 0); // on failure cores stays 0
188 return static_cast<std::size_t>(std::max(cores, 0));
189#else
190 return 0;
191#endif
192 }
193
196 [[nodiscard]] constexpr std::size_t pool_threads(std::size_t logical,
197 std::size_t physical) noexcept
198 {
199 const std::size_t hardware {std::max<std::size_t>(1, logical)};
200 return physical == 0 ? hardware : std::min(physical, hardware);
201 }
202
210 [[nodiscard]] inline std::size_t hardware_threads() noexcept
211 {
212 static const std::size_t threads {pool_threads(std::thread::hardware_concurrency(), physical_cores())};
213 return threads;
214 }
215
221 inline constexpr std::chrono::microseconds spin_time {500};
222
224 template <typename Ready>
225 void spin_until(const Ready& ready)
226 {
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();
230 }
231 }
232
234 using chunk_runner = void (*)(const void* body, std::size_t begin, std::size_t end);
235
237 using side_runner = void (*)(const void* side);
238
243 struct pool_job
244 {
246 const void* body {};
247 std::size_t count {};
248 std::size_t chunk {};
249 std::size_t chunks {};
250 std::size_t helpers {};
251 std::size_t core {no_core};
252 std::atomic<std::size_t> joined {0};
253 std::atomic<std::size_t> next {0};
254 std::atomic<std::size_t> finished {0};
255 std::mutex lock;
256 std::condition_variable done;
257 std::vector<std::exception_ptr> failed;
258
261 {
262 for (std::size_t c {next.fetch_add(1)}; c < chunks; c = next.fetch_add(1)) {
263 try {
264 run(body, c * chunk, std::min(count, (c + 1) * chunk));
265 }
266 catch (...) {
267 failed[c] = std::current_exception();
268 }
269 if (finished.fetch_add(1) + 1 == chunks) {
270 const std::lock_guard<std::mutex> held {lock};
271 done.notify_all();
272 }
273 }
274 }
275 };
276
279 {
280 explicit worker_bell(std::size_t on) : core {on}
281 {}
282
283 const std::size_t core;
284 std::atomic<bool> asleep {false};
285 std::mutex lock;
286 std::condition_variable rung;
287 };
288
291 [[nodiscard]] constexpr bool wants(std::size_t core,
292 std::size_t caller,
293 std::size_t helpers) noexcept
294 {
295 if (core == no_core) {
296 return true;
297 }
298 if (core == caller) {
299 return false;
300 }
301 const std::size_t rank {caller == no_core || core < caller ? core : core - 1};
302 return rank < helpers;
303 }
304
314 {
315 public:
316
317 thread_pool(const thread_pool&) = delete;
321 ~thread_pool() = delete;
322
329 {
330 static thread_pool* const pool {[] {
331 auto* const started {new thread_pool {}};
332 started->start();
333 return started;
334 }()};
335 return *pool;
336 }
337
345 const void* body,
346 std::size_t count,
347 std::size_t chunk,
348 std::size_t chunks,
349 std::size_t helpers,
350 side_runner side = nullptr,
351 const void* side_body = nullptr)
352 {
353 bool idle {false};
354 // A forked child has none of the parent's workers, and their locks may be held for good: it runs alone.
355 if (owner_ != current_process() || !busy_.compare_exchange_strong(idle, true)) {
356 return false;
357 }
358 struct release
359 {
360 std::atomic<bool>& busy;
361
362 explicit release(std::atomic<bool>& flag) : busy {flag}
363 {}
364
365 release(const release&) = delete;
366 release& operator=(const release&) = delete;
367 release(release&&) = delete;
368 release& operator=(release&&) = delete;
369 ~release()
370 {
371 busy.store(false);
372 }
373 } const freed {busy_};
374 const auto job {std::make_shared<pool_job>()};
375 job->run = run;
376 job->body = body;
377 job->count = count;
378 job->chunk = chunk;
379 job->chunks = chunks;
380 job->helpers = helpers;
381#if defined(__linux__)
383#endif
384 job->failed.resize(chunks);
385 {
386 const std::lock_guard<std::mutex> held {lock_};
387 current_ = job;
388 generation_.fetch_add(1);
389 }
390 for (std::size_t w {0}; w < bells_.size(); ++w) { // the workers the run wants, asleep: not the one on the caller's core
392#if defined(__linux__) // elsewhere no worker is pinned, and every one may join
393 if (!wants(bell.core, job->core, helpers)) {
394 continue;
395 }
396#endif
397 if (bell.asleep.load()) {
398 { const std::lock_guard<std::mutex> held {bell.lock}; }
399 bell.rung.notify_one();
400 }
401 }
402 std::exception_ptr side_failed;
403 if (side != nullptr) {
404 try {
406 }
407 catch (...) {
408 side_failed = std::current_exception();
409 }
410 }
411 job->work();
412 const auto all_done {[&job] { return job->finished.load() == job->chunks; }};
414 {
415 std::unique_lock<std::mutex> held {job->lock};
416 job->done.wait(held, all_done);
417 }
418 if (side_failed) {
419 std::rethrow_exception(side_failed);
420 }
421 for (const std::exception_ptr& error : job->failed) {
422 if (error) {
423 std::rethrow_exception(error);
424 }
425 }
426 return true;
427 }
428
429 private:
430
431 thread_pool() = default;
432
438 void start()
439 {
440#if defined(__linux__)
441 const std::vector<cpu_set_t> cores {core_sets()};
443 for (const std::size_t core : worker_cores(cores.size(), hardware_threads())) {
444 bells_.push_back(std::make_unique<worker_bell>(core));
445 worker_bell* const bell {bells_.back().get()};
446 std::thread {[this, cores, core, bell] {
447 pin_to_core(cores, core);
448 serve(*bell);
449 }}.detach();
450 ++workers_;
451 }
452#else
453 for (const std::size_t core : worker_cores(0, hardware_threads())) {
454 bells_.push_back(std::make_unique<worker_bell>(core));
455 worker_bell* const bell {bells_.back().get()};
456 std::thread {[this, bell] { serve(*bell); }}.detach();
457 ++workers_;
458 }
459#endif
460 }
461
465 {
466 [[maybe_unused]] const std::size_t core {bell.core};
467 std::size_t seen {0};
468#if defined(__linux__)
469 bool spin {true}; // the worker that sat a run out sleeps till the next: polling, it slowed its core's caller
470#endif
471 for (;;) {
472 const auto started {[this, &seen] { return generation_.load() != seen; }};
473#if defined(__linux__)
474 if (spin) {
476 }
477#else
479#endif
480 if (!started()) { // asleep till a run that wants it rings: one that leaves it out never wakes it
481 bell.asleep.store(true);
482 std::unique_lock<std::mutex> held {bell.lock};
483 bell.rung.wait(held, started);
484 bell.asleep.store(false);
485 }
486 std::shared_ptr<pool_job> job;
487 {
488 const std::lock_guard<std::mutex> held {lock_};
489 seen = generation_.load();
490 job = current_;
491 }
492#if defined(__linux__) // elsewhere no worker is pinned, so none sits out
493 spin = !sits_out(core, job->core);
494 if (!spin) {
495 continue;
496 }
497#endif
498#if defined(__linux__)
499 // By rank among the cores, the caller's aside, not by arrival: a run of few threads then takes the same cores
500 // from one run to the next, their caches still warm (ten AVX-512 cores, an LU of order 1024 on two threads:
501 // 21.5 → 14.6 ms, on four 18 → 11.4; six AVX2 cores unchanged).
502 const bool wanted {wants(core, job->core, job->helpers)};
503#else
504 const bool wanted {true};
505#endif
506 if (wanted && job->joined.fetch_add(1) < job->helpers) { // as many workers as the run's threads, the caller aside
507 job->work();
508 }
509 }
510 }
511
512 std::mutex lock_;
513 std::vector<std::unique_ptr<worker_bell>> bells_;
514 std::shared_ptr<pool_job> current_;
515 std::atomic<std::size_t> generation_ {0};
516 std::atomic<bool> busy_ {false};
517 std::size_t workers_ {0};
518#if defined(__linux__)
519 std::vector<std::size_t> processor_cores_;
520#endif
522 };
523
525 [[nodiscard]] inline std::atomic<std::size_t>& thread_cap() noexcept
526 {
527 static std::atomic<std::size_t> cap {0};
528 return cap;
529 }
530
534 inline constexpr std::size_t work_per_thread {std::size_t {1} << 16};
535
539 inline constexpr std::size_t elementwise_chunk {work_per_thread / 4};
540
544 inline constexpr std::size_t vector_speedup {4};
545
553 [[nodiscard]] inline std::size_t threads_for(std::size_t count,
554 std::size_t work_per_index,
555 std::size_t hardware)
556 {
557 const std::size_t total {work_per_index != 0 && count > std::numeric_limits<std::size_t>::max() / work_per_index
558 ? std::numeric_limits<std::size_t>::max()
559 : count * work_per_index};
560 return std::min({total / work_per_thread, count, std::max<std::size_t>(1, hardware)});
561 }
562
579 template <typename Body>
580 void parallel_for(std::size_t count,
581 std::size_t work_per_index,
582 const Body& body)
583 {
584 const std::size_t cap {thread_cap().load()};
585 const std::size_t threads {threads_for(count, work_per_index, cap == 0 ? hardware_threads() : std::min(cap, hardware_threads()))};
586 const auto runner {[](const void* erased, std::size_t begin, std::size_t end) {
587 const Body& work {*static_cast<const Body*>(erased)};
588 for (std::size_t i {begin}; i < end; ++i) {
589 work(i);
590 }
591 }};
592 if (threads < 2) {
593 runner(&body, 0, count);
594 return;
595 }
596 // Up to four chunks a thread, taken as threads free up, so a slower core holds back no one.
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}; // a last chunk past the count runs nothing
599 if (!thread_pool::shared().try_run(runner, &body, count, chunk, chunks, threads - 1)) {
600 runner(&body, 0, count);
601 }
602 }
603
616 template <typename Body,
617 typename Side>
618 void parallel_for_beside(std::size_t count,
619 std::size_t work_per_index,
620 const Body& body,
621 const Side& side)
622 {
623 const std::size_t cap {thread_cap().load()};
624 const std::size_t threads {threads_for(count, work_per_index, cap == 0 ? hardware_threads() : std::min(cap, hardware_threads()))};
625 const auto runner {[](const void* erased, std::size_t begin, std::size_t end) {
626 const Body& work {*static_cast<const Body*>(erased)};
627 for (std::size_t i {begin}; i < end; ++i) {
628 work(i);
629 }
630 }};
631 const auto beside {[](const void* erased) { (*static_cast<const Side*>(erased))(); }};
632 if (threads >= 2) {
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};
635 if (thread_pool::shared().try_run(runner, &body, count, chunk, chunks, threads - 1, beside, &side)) {
636 return;
637 }
638 }
639 side();
640 runner(&body, 0, count);
641 }
642
644 inline constexpr std::size_t zero_chunk {std::size_t {1} << 12};
645
653 template <typename T>
654 [[nodiscard]] constexpr std::size_t fault_chunk(std::size_t count) noexcept
655 {
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};
659 if (count >= huge_least / sizeof(T)) {
660 return huge_page / sizeof(T);
661 }
662#else
663 (void)count;
664#endif
665 return zero_chunk;
666 }
667
671 inline constexpr std::size_t zero_work {1};
672
681 template <typename T>
683 std::size_t count)
684 {
685 if (count < 2 * (work_per_thread / zero_work)) { // too little to pay for a second thread
686 std::fill(out, out + count, T {});
687 return;
688 }
689 const std::size_t piece {fault_chunk<T>(count)};
690 parallel_for((count + piece - 1) / piece, piece * zero_work, [out, count, piece](std::size_t chunk) {
691 const std::size_t begin {chunk * piece};
692 std::fill(out + begin, out + std::min(count, begin + piece), T {});
693 });
694 }
695
702 template <typename T>
703 void parallel_copy(const T* in,
704 std::size_t count,
705 T* out)
706 {
707 if (count < 2 * (work_per_thread / zero_work)) { // too little to pay for a second thread
708 std::copy(in, in + count, out);
709 return;
710 }
711 const std::size_t piece {fault_chunk<T>(count)};
712 parallel_for((count + piece - 1) / piece, piece * zero_work, [in, out, count, piece](std::size_t chunk) {
713 const std::size_t begin {chunk * piece};
714 std::copy(in + begin, in + std::min(count, begin + piece), out + begin);
715 });
716 }
717} // namespace numerical::detail
718
719namespace numerical {
720
726 inline void set_max_threads(std::size_t threads) noexcept
727 {
728 detail::thread_cap().store(threads);
729 }
730
732 [[nodiscard]] inline std::size_t max_threads()
733 {
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);
737 }
738} // namespace numerical
739
740#endif // NUMERICAL_PARALLEL_HPP
The threads every kernel shares, started once: a parallel_for costs a wake-up, not a thread start.
Definition parallel.hpp:314
std::mutex lock_
Guards current_ and generation_.
Definition parallel.hpp:512
thread_pool & operator=(thread_pool &&)=delete
thread_pool & operator=(const thread_pool &)=delete
std::shared_ptr< pool_job > current_
The latest run.
Definition parallel.hpp:514
static thread_pool & shared()
The pool, started on first use with a worker per hardware thread beside the caller.
Definition parallel.hpp:328
thread_pool(thread_pool &&)=delete
std::size_t workers_
The threads started.
Definition parallel.hpp:517
thread_pool(const thread_pool &)=delete
std::atomic< bool > busy_
Whether a run holds the pool.
Definition parallel.hpp:516
void start()
Starts the workers (after construction: each uses the pool), worker_cores: on Linux one per core,...
Definition parallel.hpp:438
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,...
Definition parallel.hpp:344
std::vector< std::unique_ptr< worker_bell > > bells_
Each worker's, set before it starts.
Definition parallel.hpp:513
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,...
Definition parallel.hpp:464
long owner_
The process whose threads they are.
Definition parallel.hpp:521
std::atomic< std::size_t > generation_
Counts the runs (polled without the lock).
Definition parallel.hpp:515
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.
Definition parallel.hpp:59
constexpr std::size_t zero_chunk
The elements one index of parallel_zero writes: eight 4 KiB pages of double.
Definition parallel.hpp:644
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 ...
Definition parallel.hpp:682
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),...
Definition parallel.hpp:618
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...
Definition parallel.hpp:78
constexpr std::size_t zero_work
The work of zeroing one element, in multiply-adds: a second thread from 1 MiB of double....
Definition parallel.hpp:671
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...
Definition parallel.hpp:66
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...
Definition parallel.hpp:544
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...
Definition parallel.hpp:196
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...
Definition parallel.hpp:291
void(*)(const void *side) side_runner
A task the caller of a run does before it takes chunks (parallel_for_beside), type-erased.
Definition parallel.hpp:237
constexpr std::size_t work_per_thread
The work one thread must have to pay for starting it: about 60 µs of multiply-adds,...
Definition parallel.hpp:534
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,...
Definition parallel.hpp:703
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...
Definition parallel.hpp:539
constexpr std::chrono::microseconds spin_time
How long a thread polls before it sleeps: kernels run in quick succession (a matrix product packs,...
Definition parallel.hpp:221
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,...
Definition parallel.hpp:553
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).
Definition parallel.hpp:234
void spin_until(const Ready &ready)
Polls ready, yielding the core between polls, until it holds or spin_time has passed.
Definition parallel.hpp:225
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).
Definition parallel.hpp:580
long current_process() noexcept
This process's identifier: a pool started before a fork belongs to the parent, whose threads the chil...
Definition parallel.hpp:49
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.
Definition parallel.hpp:525
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...
Definition parallel.hpp:180
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...
Definition parallel.hpp:210
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,...
Definition parallel.hpp:654
The NumericAL public API.
Definition amx.hpp:44
std::size_t max_threads()
The threads a kernel may use: the cap if one is set, else the hardware threads (starting none).
Definition parallel.hpp:732
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,...
Definition parallel.hpp:726
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....
Definition parallel.hpp:244
chunk_runner run
The chunk runner.
Definition parallel.hpp:245
std::atomic< std::size_t > finished
The chunks done.
Definition parallel.hpp:254
std::size_t chunk
Indices per chunk.
Definition parallel.hpp:248
std::size_t count
The number of iterations.
Definition parallel.hpp:247
std::mutex lock
Orders the last chunk's signal against the caller's sleep.
Definition parallel.hpp:255
std::size_t core
The caller's core: its worker sits it out (sits_out).
Definition parallel.hpp:251
std::atomic< std::size_t > joined
The workers that asked to.
Definition parallel.hpp:252
std::size_t helpers
The workers that may join it beside the caller.
Definition parallel.hpp:250
std::size_t chunks
Chunks.
Definition parallel.hpp:249
std::atomic< std::size_t > next
The next chunk to take.
Definition parallel.hpp:253
void work() noexcept
Takes chunks until none is left, keeping the exception each throws.
Definition parallel.hpp:260
std::condition_variable done
Signalled when the last chunk finishes.
Definition parallel.hpp:256
const void * body
The body it is handed.
Definition parallel.hpp:246
std::vector< std::exception_ptr > failed
The exception of each chunk, if it threw.
Definition parallel.hpp:257
A worker's own wake-up: its core, whether it sleeps, and what wakes it.
Definition parallel.hpp:279
const std::size_t core
The core it is pinned to, or no_core.
Definition parallel.hpp:283
std::condition_variable rung
Signalled when a run that wants it starts.
Definition parallel.hpp:286
std::atomic< bool > asleep
Set before it checks for a run and sleeps.
Definition parallel.hpp:284
std::mutex lock
Orders a ring against its sleep.
Definition parallel.hpp:285