HPX - High Performance ParalleX

PrevUpHomeNext

Executors and Executor Traits

The existing Version 1 of the Parallelism TS (N4409) exposes parallel execution to the programmer in the form of standard algorithms that accept execution policies. A companion executor facility both provides a suitable substrate for implementing these algorithms in a standard way and provide a mechanism for exercising programmatic control over where parallel work should be executed.

The algorithms and execution policies specified by the Parallelism TS are designed to permit implementation on the broadest range of platforms. In addition to preemptive thread pools common on some platforms, implementations of these algorithms may want to take advantage of a number of mechanisms for parallel execution, including cooperative fibers, GPU threads, and SIMD vector units, among others. This diversity of possible execution resources strongly suggests that a suitable abstraction encapsulating the details of how work is created across diverse platforms would be of significant value to parallel algorithm implementations. Suitably defined executors provide just such a facility.

An executor is an object responsible for creating execution agents on which work is performed, thus abstracting the (potentially platform-specific) mechanisms for launching work. To accommodate the goals of the Parallelism TS, whose algorithms aim to support the broadest range of possible platforms, the requirements that all executors are expected to fulfill are small. They are also be consistent with a broad range of execution semantics, including preemptive threads, cooperative fibers, GPU threads, and SIMD vector units, among others.

The executors implemented by HPX are aligned with the interfaces proposed by N4406 (Parallel Algorithms Need Executors).

Executors are modular components for requisitioning execution agents. During parallel algorithm execution, execution policies generate execution agents by requesting their creation from an associated executor. Rather than focusing on asynchronous task queueing, our complementary treatment of executors casts them as modular components for invoking functions over the points of an index space. We believe that executors may be conceived of as allocators for execution agents and our interface's design reflects this analogy. The process of requesting agents from an executor is mediated via the hpx::parallel::executor_traits API, which is analogous to the interaction between containers and allocator_traits.

With executor_traits, clients manipulate all types of executors uniformly:

executor_traits<my_executor_t>::execute(my_executor,
    [](size_t i){ // perform task i },
    range(0, n));

This call synchronously creates a group of invocations of the given function, where each individual invocation within the group is identified by a unique integer i in [0, n). Other functions in the interface exist to create groups of invocations asynchronously and support the special case of creating a singleton group, resulting in four different combinations.

Though this interface appears to require executor authors to implement four different basic operations, there is really only one requirement: async_execute(). In practice, the other operations may be defined in terms of this single basic primitive. However, some executors will naturally specialize all four operations for maximum efficiency.

For maximum implementation flexibility, executor_traits does not require executors to implement a particular exception reporting mechanism. Executors may choose whether or not to report exceptions, and if so, in what manner they are communicated back to the caller. However, all executors in HPX report exceptions in a manner consistent with the behavior of execution policies described by the Parallelism TS, where multiple exceptions are collected into an exception_list. This list is reported through async_execute()'s returned future, or thrown directly by execute().

In HPX we have implemented the following executor types:


PrevUpHomeNext