|
Orrery
A GPU-accelerated N-body gravitational simulator
|
What each worker thread did, and how long it spent doing nothing. More...
Go to the source code of this file.
Classes | |
| struct | orrery::backend::WorkerStatistics |
| One worker's record, accumulated over every region since the last reset. More... | |
| struct | orrery::backend::PaddedWorkerStatistics |
| One worker's record, on a cache line of its own. More... | |
| struct | orrery::backend::ExecutorStatistics |
| The whole pool's record. More... | |
Typedefs | |
| using | orrery::backend::Clock = std::chrono::steady_clock |
| The clock every duration here is measured on. | |
| using | orrery::backend::Duration = std::chrono::nanoseconds |
| Durations are stored in nanoseconds so that they add without conversion and mean the same thing on every platform, whatever the clock's native period is. | |
What each worker thread did, and how long it spent doing nothing.
Phase 6 exists to answer a question that a speedup figure cannot: when eight threads run on four performance cores and four efficiency cores, how much of the wall time do the fast cores spend waiting for the slow ones. A scheduler that hides that behind a single number is a scheduler nobody can improve, so the instrumentation is part of the design rather than something bolted on for one benchmark.
The accounting is deliberately simple and closed. Within one parallel region every worker is either running a chunk of the caller's work or it is not, and the region lasts from the moment the submitting thread hands the work over to the moment the last worker finishes. So for each worker
busy + idle = the duration of the region
and summing over W workers gives W times the region duration. Idle time measured this way includes everything that is not the caller's arithmetic: the wake-up latency of a sleeping thread, the wait for a chunk to become available, and the wait at the end for slower workers to finish. That is deliberate. Those costs are real, they are what a threading scheme is judged on, and a definition that excluded them would flatter the scheduler by measuring only the part of it that was already working.
The cost of the measurement is two clock readings per chunk. Chunks are sized so that each is tens of microseconds of work at the sizes this project runs at, against roughly a hundred nanoseconds for the pair of readings, so the instrumentation is under a part in a hundred and is left permanently on. That is worth more than the last fraction of a percent: a scheduler whose behaviour can only be seen in a special build is a scheduler whose behaviour is not seen.
| using orrery::backend::Clock = std::chrono::steady_clock |
The clock every duration here is measured on.
steady_clock rather than high_resolution_clock, which on some standard libraries is an alias for the system clock and therefore moves when the wall clock is adjusted. An interval that can go backwards is not a measurement.