|
Orrery
A GPU-accelerated N-body gravitational simulator
|
How a kernel asks for a loop to be run in parallel. More...
#include <string_view>#include "orrery/backend/cpu_topology.hpp"#include "orrery/backend/worker_statistics.hpp"#include "orrery/core/function_ref.hpp"#include "orrery/core/types.hpp"Go to the source code of this file.
Classes | |
| class | orrery::backend::Executor |
| Something that can run a loop body over a range, possibly in parallel. More... | |
Typedefs | |
| using | orrery::backend::RangeTask = core::FunctionRef<void(core::Index, core::Index)> |
| A piece of a parallel loop: the half-open index range [begin, end). | |
How a kernel asks for a loop to be run in parallel.
Every parallelisable kernel in this project has the same shape. There is a range of particle indices, the work at each index is independent of the work at every other, and each index writes only its own outputs. ADR-0015 made that true of the direct solver on purpose, by having each particle read every other and write only itself, and Phase 8's tree walk will have the same property for the same reason. So the whole of what the kernels need from a scheduler is: divide [0, count) among some threads and call this for each piece.
That is the entire interface. There is no task graph, no dependency tracking, no nested spawning and no future to wait on, because no kernel here needs any of them, and a scheduler general enough to express them would be harder to reason about and slower at the one thing it actually does.
The implementations differ in how they divide the range, which is precisely the question Phase 6 was written to answer, and they are selected at run time so that a benchmark can measure one against another in the same process on the same configuration. The virtual call happens once per force evaluation, ahead of N^2 interactions, which is the boundary section 3 of the implementation plan permits dispatch at.
A task must not throw. Section 4 forbids an exception leaving a kernel, and here the rule has teeth beyond style: the task runs on a worker thread, and an exception escaping it would cross a thread boundary with nowhere to be caught. The worker entry points are noexcept, so such an exception terminates the program at the point it was thrown rather than corrupting the pool's bookkeeping on the way out.
| using orrery::backend::RangeTask = core::FunctionRef<void(core::Index, core::Index)> |
A piece of a parallel loop: the half-open index range [begin, end).
A reference rather than a std::function because it is called across a virtual boundary once per force evaluation and owns nothing that outlives the call. core/function_ref.hpp sets out the reasoning and the lifetime rule that comes with it.