3#include <citor/cancellation.h>
4#include <citor/hints.h>
5#include <citor/thread_pool.h>
14#include <unordered_map>
18#include "clustering/math/detail/coherence_cache.h"
61 const std::size_t hw = std::thread::hardware_concurrency();
62 return hw == 0 ? std::size_t{1} : hw;
83 std::size_t minOpsPerWorker = std::size_t{1} << 15)
noexcept {
87 return (totalOps / nJobs) >= minOpsPerWorker;
119 if (
pool ==
nullptr) {
120 return std::size_t{1};
122 return pool->participants();
132 return citor::ThreadPool::workerIndex();
148 std::size_t minTasksPerWorker = 2) const noexcept {
149 if (
pool ==
nullptr || minChunk == 0) {
152 return (totalWork / minChunk) >= (
workerCount() * minTasksPerWorker);
169 std::size_t minOpsPerWorker = std::size_t{1}
170 << 15)
const noexcept {
171 if (
pool ==
nullptr) {
174 return (totalOps /
workerCount()) >= minOpsPerWorker;
192 std::size_t minRowsPerBlock = 256) const noexcept {
194 if (workers <= 1 || n == 0) {
197 const std::size_t byRows =
198 (minRowsPerBlock == 0) ? n : std::max<std::size_t>(1, n / minRowsPerBlock);
199 const std::size_t blocks = std::min(workers * 8, byRows);
200 return std::max(blocks, workers);
212 template <
class H
intsT = citor::H
intsDefaults,
class FnA,
class FnB>
214 if (
pool ==
nullptr) {
219 pool->template forkJoin<HintsT>(std::forward<FnA>(a), std::forward<FnB>(b));
238 template <
class H
intsT = citor::H
intsDefaults,
class Body>
240 if (
pool ==
nullptr || first >= last) {
244 if (numBlocks != 0) {
248 pool->template parallelFor<HintsT>(first, last, body);
267 template <
class H
intsT = citor::H
intsDefaults,
class Body>
270 if (first >= last || numBlocks == 0) {
273 if (
pool ==
nullptr || numBlocks == 1) {
277 const std::size_t span = last - first;
278 auto sliceLo = [first, span, numBlocks](std::size_t s)
noexcept {
279 return first + ((span * s) / numBlocks);
281 pool->template parallelFor<HintsT>(std::size_t{0}, numBlocks,
282 [&](std::size_t loSlot, std::size_t hiSlot) {
283 for (std::size_t s = loSlot; s < hiSlot; ++s) {
284 const std::size_t blockLo = sliceLo(s);
285 const std::size_t blockHi = sliceLo(s + 1);
286 body(blockLo, blockHi);
306 template <
class H
intsT = citor::H
intsDefaults,
class Body>
309 if (first >= last || numBlocks == 0) {
312 if (
pool ==
nullptr || numBlocks == 1) {
313 body(first, last, std::size_t{0});
316 const std::size_t span = last - first;
317 auto sliceLo = [first, span, numBlocks](std::size_t s)
noexcept {
318 return first + ((span * s) / numBlocks);
320 pool->template parallelFor<HintsT>(std::size_t{0}, numBlocks,
321 [&](std::size_t loSlot, std::size_t hiSlot) {
322 for (std::size_t s = loSlot; s < hiSlot; ++s) {
323 body(sliceLo(s), sliceLo(s + 1), s);
341 template <
class H
intsT = citor::H
intsDefaults,
class Body>
343 if (
pool ==
nullptr || numChunks == 0) {
344 for (std::size_t c = 0; c < numChunks; ++c) {
349 pool->template parallelFor<HintsT>(std::size_t{0}, numChunks,
350 [&](std::size_t lo, std::size_t hi) {
351 for (std::size_t c = lo; c < hi; ++c) {
375 template <
class H
intsT = citor::H
intsDefaults,
class T,
class Map,
class Combine>
376 [[nodiscard]] T
parallelReduce(std::size_t first, std::size_t last, T init, Map map,
381 if (
pool ==
nullptr) {
382 return combine(std::move(init), map(first, last));
410 template <
class H
intsT = citor::H
intsDefaults,
class Phase>
412 auto noPrePhase = [](std::size_t )
noexcept {};
443 template <
class H
intsT = citor::H
intsDefaults,
class T,
class BodyFn,
class PrefixFn>
444 T
parallelScan(std::size_t n, T identity, BodyFn body, PrefixFn prefix) {
448 if (
pool ==
nullptr) {
449 T partial = body(std::size_t{0}, std::size_t{0}, n, identity,
static_cast<T *
>(
nullptr));
450 return prefix(std::move(identity), std::move(partial));
472 template <
class H
intsT = citor::H
intsDefaults,
class T,
class PrefixFn>
473 [[nodiscard]] T
inclusiveScan(std::span<const T> in, std::span<T> out, T identity,
475 if (
pool ==
nullptr) {
477 for (std::size_t i = 0; i < in.size(); ++i) {
478 acc = prefix(acc, in[i]);
493 template <
class H
intsT = citor::H
intsDefaults,
class Phase,
class PrePhase>
494 void parallelRunPlex(std::size_t nPhases, std::size_t n, Phase phaseFn, PrePhase prePhaseFn,
495 citor::CancellationToken tok = citor::CancellationToken{}) {
499 if (
pool ==
nullptr) {
502 for (std::size_t p = 0; p < nPhases; ++p) {
503 if (tok.stop_requested()) {
507 phaseFn(p, std::uint32_t{0}, std::size_t{0}, n,
static_cast<void *
>(
nullptr));
511 pool->template runPlex<HintsT>(nPhases, n, std::forward<Phase>(phaseFn),
512 std::forward<PrePhase>(prePhaseFn), std::move(tok));
521 static std::unordered_map<std::size_t, std::unique_ptr<OwnedPool>> registry;
522 static std::mutex registryMutex;
523 const std::scoped_lock guard{registryMutex};
524 auto &slot = registry[effective];
530 const bool seeded = detail::importPersistedCoherenceProbe(effective);
531 slot = std::make_unique<OwnedPool>(effective);
533 detail::exportPersistedCoherenceProbe(*slot, effective);
std::size_t clampedJobCount(std::size_t nJobs) noexcept
Clamp a caller-supplied nJobs to a valid worker count.
bool shouldSpawnPool(std::size_t totalOps, std::size_t nJobs, std::size_t minOpsPerWorker=std::size_t{1}<< 15) noexcept
Decide whether spawning a pool with nJobs workers is worth it for totalOps of arithmetic work.
citor::ThreadPool OwnedPool
Type alias for the owning pool the algorithm wrappers (KMeans, DBSCAN, HDBSCAN) hold inside an std::o...
OwnedPool & sharedPool(std::size_t nJobs)
Process-wide pool registry, keyed by worker count.
Thin compile-time-templated wrapper around the underlying OwnedPool.
static std::size_t workerIndex() noexcept
Stable index of the calling worker thread within the owning pool.
void parallelRunPlex(std::size_t nPhases, std::size_t n, Phase phaseFn)
Run phaseFn for nPhases persistent-worker phases over [0, n).
OwnedPool * pool
Underlying pool, or nullptr to force serial execution.
void parallelForExactBlocks(std::size_t first, std::size_t last, std::size_t numBlocks, Body body)
Run body in parallel with exactly numBlocks contiguous ranges.
std::size_t workerCount() const noexcept
Number of worker threads available, or 1 in serial mode.
void parallelForChunks(std::size_t numChunks, Body body)
Run body once per chunk over [0, numChunks) in parallel.
void forkJoin2(FnA &&a, FnB &&b) const
Run two independent tasks as a fork-join pair.
void parallelRunPlex(std::size_t nPhases, std::size_t n, Phase phaseFn, PrePhase prePhaseFn, citor::CancellationToken tok=citor::CancellationToken{})
Pre-phase form of parallelRunPlex with cooperative cancellation.
void parallelForBlocks(std::size_t first, std::size_t last, std::size_t numBlocks, Body body)
Run body in parallel over [first, last) partitioned into numBlocks blocks.
std::size_t stealBlocks(std::size_t n, std::size_t minRowsPerBlock=256) const noexcept
Block count for a fan-out over n rows that lets work-stealing balance heterogeneous cores.
T inclusiveScan(std::span< const T > in, std::span< T > out, T identity, PrefixFn prefix)
Buffer-to-buffer inclusive prefix scan of in into out.
bool shouldParallelize(std::size_t totalWork, std::size_t minChunk, std::size_t minTasksPerWorker=2) const noexcept
Decide whether totalWork warrants parallel dispatch.
bool shouldParallelizeWork(std::size_t totalOps, std::size_t minOpsPerWorker=std::size_t{1}<< 15) const noexcept
Decide whether totalOps warrants parallel dispatch, based on work volume.
void parallelForExactBlocksWithSlot(std::size_t first, std::size_t last, std::size_t numBlocks, Body body)
Slot-aware variant of parallelForExactBlocks.
T parallelScan(std::size_t n, T identity, BodyFn body, PrefixFn prefix)
Two-pass exclusive-prefix scan over [0, n).
T parallelReduce(std::size_t first, std::size_t last, T init, Map map, Combine combine)
Reduce [first, last) with the backend's reduction primitive.