Orrery
A GPU-accelerated N-body gravitational simulator
Loading...
Searching...
No Matches
orrery::backend::WorkStealingExecutor Class Referencefinal

Divides the range into equal shares and lets idle workers take from busy ones. More...

#include <orrery/backend/work_stealing_executor.hpp>

Inheritance diagram for orrery::backend::WorkStealingExecutor:
orrery::backend::Executor

Public Member Functions

 WorkStealingExecutor (unsigned worker_count=0, ThreadPool::Affinity affinity=ThreadPool::Affinity::kUnpinned)
void run (core::Index count, RangeTask task) override
 Call task over pieces that together cover [0, count) exactly once each, and return when all of them have finished.
std::string_view name () const noexcept override
 The scheme's name, for benchmark tables and reports.
unsigned worker_count () const noexcept override
 How many workers this executor divides work between.
CoreClass worker_core_class (unsigned worker) const noexcept override
 What kind of core a worker runs on, where that is known.
ExecutorStatistics statistics () const override
 The record of what the workers have done since the last reset.
void reset_statistics () noexcept override
 Set every counter back to zero.

Static Public Attributes

static constexpr core::Index kChunksPerWorker = 16
 How many chunks each worker's share is broken into.

Additional Inherited Members

Protected Member Functions inherited from orrery::backend::Executor
 Executor (const Executor &)=default
 Executor (Executor &&)=default
Executor & operator= (const Executor &)=default
Executor & operator= (Executor &&)=default

Detailed Description

Divides the range into equal shares and lets idle workers take from busy ones.

Member Function Documentation

◆ run()

void orrery::backend::WorkStealingExecutor::run ( core::Index count,
RangeTask task )
overridevirtual

Call task over pieces that together cover [0, count) exactly once each, and return when all of them have finished.

The division is the implementation's to choose, and so is the number of threads it uses and the order the pieces run in. A caller may rely on each index being covered exactly once and on all the work being complete on return, and on nothing else.

A count of zero runs nothing. An empty configuration is a configuration, and it reaches the solvers by the same path as any other.

Implements orrery::backend::Executor.

◆ name()

std::string_view orrery::backend::WorkStealingExecutor::name ( ) const
inlinenodiscardoverridevirtualnoexcept

The scheme's name, for benchmark tables and reports.

Implements orrery::backend::Executor.

◆ worker_count()

unsigned orrery::backend::WorkStealingExecutor::worker_count ( ) const
inlinenodiscardoverridevirtualnoexcept

How many workers this executor divides work between.

Implements orrery::backend::Executor.

◆ worker_core_class()

CoreClass orrery::backend::WorkStealingExecutor::worker_core_class ( unsigned worker) const
inlinenodiscardoverridevirtualnoexcept

What kind of core a worker runs on, where that is known.

kUnknown unless the executor pinned its workers, because without pinning the operating system is free to move a thread between a performance and an efficiency core mid-region, and an answer that described where the thread started would be a guess about where it spent its time. Phase 6's per-core-class figures come from pinned runs for exactly this reason.

Implements orrery::backend::Executor.

◆ statistics()

ExecutorStatistics orrery::backend::WorkStealingExecutor::statistics ( ) const
nodiscardoverridevirtual

The record of what the workers have done since the last reset.

Safe to call only when no region is running, which for a single submitting thread means any time run is not on the stack. It copies the per-worker records rather than returning a view of them, because a caller comparing two schemes needs the first scheme's numbers to survive the second scheme running.

Implements orrery::backend::Executor.

◆ reset_statistics()

void orrery::backend::WorkStealingExecutor::reset_statistics ( )
overridevirtualnoexcept

Set every counter back to zero.

A benchmark measures the region it timed rather than the warm-up before it, and once the two have been added together they cannot be separated.

Implements orrery::backend::Executor.

Member Data Documentation

◆ kChunksPerWorker

core::Index orrery::backend::WorkStealingExecutor::kChunksPerWorker = 16
staticconstexpr

How many chunks each worker's share is broken into.

This is the one tuning constant in the scheduler and it is a compromise between two costs. Larger chunks mean fewer lock acquisitions and fewer clock readings, but a coarser tail: the region cannot finish sooner than the last chunk started, so the residual imbalance is about one chunk. Smaller chunks balance more finely and cost more to hand out.

Sixteen puts the residual imbalance at roughly one part in sixteen of a worker's share, which is well under the two-to-one throughput difference between the core types this is here to absorb, while keeping the per-chunk overhead near a thousandth of the chunk. The value is not finely tuned and does not need to be: anything from about eight to sixty-four behaves the same on this machine, which is the sign of a constant sitting in the flat part of the curve rather than on a peak.


The documentation for this class was generated from the following file: