-
Notifications
You must be signed in to change notification settings - Fork 0
Creation of conditional deferred work
DARMA allows to create deferred work that is executed only if certain conditions are met. Such construct is equivalent to if-then-else
statements in a generic programming language. Conditional concurrent work is created by calling function create_work_if
, which is defined in darma/interface/app/create_work_if.h. The signature of this function is:
template <typename Functor = meta::nonesuch, typename... Args>
auto create_work_if(Args&& ... args);
create_work_if
can be invoked using either a functor or a lambda. When a functor is used, its type should be explicitly specified. The call to create_work_if
creates and returns an object of type _create_work_if_helper
. This is a templated structure that is specialized for functors and lambdas. The two specializations are defined in darma/impl/create_work/create_if_then_functor.h and darma/impl/create_work/create_if_then_lambda.h respectively. This object is created by simply forwarding args
, the variadic pack of arguments used to invoke create_work_if
. A detailed description of _create_work_if_helper
is provided in the following sections. For the sake of simplicity, the subsequent discussion will focus on the functor version. Before continuing, it is worth noting that create_work_if
is used to specify the conditions to check in deciding whether to perform deferred work or not. The deferred work to be executed if such conditions are satisfied should be specified by calling method then_
on the object returned by create_work_if
. Calls to create_work_if
should have the following appearance:
create_work_if<FunctorType_If>(args_if...).then_<FunctorType_Then>(args_then...);
Similarly to an if-then-else
statement, an else
condition can also be specified by invoking else_
on the object returned by the call to then_
.
_create_work_if_helper
is a templated struct that is specialized for functors and lambdas. This struct has the following public members:
static constexpr auto is_lambda_callable;
std::tuple<Args&&...> args_fwd_tup;
where is_lambda_callable
is set to false
(true
) for the functor (lambda) case, and args_fwd_tup
stores the arguments used to invoke create_work_if
. The constructor of _create_work_if_helper
, which is invoked by create_work_if
, simply initializes args_fwd_tup
using perfect forwarding. _create_work_if_helper
also has a templated member function then_
. The signature of this function is:
template<typename ThenFunctor=meta::nonesuch, typename... ThenArgs>
auto then_(ThenArgs&&... args) &&
As mentioned in the previous section, then_<FunctorType_Then>
is used to specify the deferred work to be executed should the conditions specified in the if
section of the create_work_if
construct be satisfied. then_
takes as input a variadic pack of arguments, corresponding to the arguments passed to the functor associated with the then
section of the construct. This function creates an object of type _create_work_then_helper
by perfect forwarding its input arguments and returns the object so created. As it will become clearer in the following paragraphs, this object is responsible for triggering the capture mechanism , should a else_
clause not be provided, or to allow the specification of such clause.
It was mentioned that method then_
of _create_work_if_helper
is used to specify the deferred work to be conditionally executed should the if
clause be satisfied. This method takes as inputs the arguments of the functor / lambda to be conditionally executed and use them to create and return an object of type _create_work_then_helper
. This struct, which is defined in darma/impl/create_work/create_if_then_functor.h (darma/impl/create_work/create_if_then_lambda.h for lambda tasks), is templated on IfHelper
, the _create_work_if_helper
struct used to create it, Functor
, the functor to be conditionally executed, and Args
, the arguments' types of such functor. The member variables of this struct are:
-
IfHelper if_helper
: stores the instance of_create_work_if_helper
used to initialize_create_work_then_helper
; -
args_fwd_tuple_t args_fwd_tup
: tuple of the arguments used to initialize_create_work_if_helper
; -
bool else_invoked
: defaulted tofalse
, it is set totrue
if an alternative deferred work is specified through anelse
clause. This can be done by invoking methodelse_
of_create_work_then_helper
.
Other than its constructor and method else_
, the definition of _create_work_then_helper
also provides a non-trivial destructor. Such destructor is in charge of triggering the capture mechanism and registering the task associated with the if
clause, should an else
clause not be provided. If an else
clause is provided, these responsibilities are assigned to the destructor of _create_work_else_helper
, the type of the object returned by method else_
. Assuming method else_
is not invoked, the destructor of _create_work_then_helper
creates a shared pointer to an object of type IfThenElseCaptureManager
and then call its method finish_construction_and_register_if_task
. The IfThenElseCaptureManager
class will be described in the following section. Before proceeding, it is worth summarizing the operations performed during create_work_if
. A call to this method returns an object of type _create_work_if_helper
, which is then used to create a second object of type _create_work_then_helper
by calling its method then_
. If desired, this second object can be used to create a third object, _create_work_else_helper
, by calling its method else_
. This object remains anonymous and is destroyed immediately after being created. Its destruction triggers the capture mechanism, whose implementation will be discussed next.
The IfThenElseCaptureManager
struct is defined in darma/impl/create_work/create_if_then.h. Its specialization for functors is:
template <typename IfCallableT, typename... IfArgsT, bool IfIsLambda,
typename ThenCallableT, typename... ThenArgsT, bool ThenIsLambda,
typename ElseCallableT, typename... ElseArgsT, bool ElseIsLambda,
bool ElseGiven
>
struct IfThenElseCaptureManager<IfCallableT, std::tuple<IfArgsT...>, IfIsLambda,
ThenCallableT, std::tuple<ThenArgsT...>, ThenIsLambda,
ElseCallableT, std::tuple<ElseArgsT...>, ElseIsLambda,
ElseGiven
> : public CaptureManager, public IfThenElseCaptureManagerSetupHelper
The template parameters of IfThenElseCaptureManager
are:
-
IfCallableT
: type of the callable (functor in this case) associated with theif
section of theif-then-else
construct; -
std::tuple<IfArgsT...>
: tuple of the arguments types taken byIfCallableT
; -
IfIsLambda
: boolean indicating whetherIfCallableT
is a functor or a lambda; -
ThenCallableT
: type of the callable (functor in this case) associated with thethen
section of theif-then-else
construct; -
std::tuple<ThenArgsT...>
: tuple of the arguments types taken byThenCallableT
; -
ThenIsLambda
: boolean indicating whetherThenCallableT
is a functor or a lambda; -
ElseCallableT
: type of the callable (functor in this case) associated with theelse
section of theif-then-else
construct; -
std::tuple<ElseArgsT...>
: tuple of the arguments types taken byElseCallableT
; -
ElseIsLambda
: boolean indicating whetherElseCallableT
is a functor or a lambda; -
ElseGiven
: boolean indicating whether anelse
section has been given or not.
IfThenElseCaptureManager
has the following private members:
-
std::unique_ptr<if_task_t> if_task_
; -
std::unique_ptr<then_task_t> then_task_
; -
std::unique_ptr<else_task_t> else_task_
.
Here, if_task_t
, then_task_t
and else_task_t
are private type aliases that correspond to either IfFunctorTask
, ThenFunctorTask
, ElseFunctorTask
, or IfLambdaTask
, ThenLambdaTask
, ElseLambdaTask
respectively, depending on the values of IfIsLambda
, ThenIsLambda
and ElseIsLambda
. The members if_task_
, then_task_
and else_task_
are initialized during the construction of IfThenElseCaptureManager
, with else_task_
being set to nullptr
if no else_
section was defined. The types aliased by if_task_t
, then_task_t
and else_task_t
inherits from either FunctorTask
or LambdaTask
: their creation will trigger the dependency capture mechanism. Although this mechanism is similar to the one described for create_work
, there is a fundamental difference that stem from the use of IfThenElseCaptureManager
as the capture manager in create_work_if
. Method do_capture
now simply stores the captured and source and continuing access handles for the if
, then_
and else_
sections in three distinct std::map
objects, if_captures_
, then_captures_
and else_captures_
. The actual creation of the captured use holder object and its registration as a task dependency is done in a later stage: this is made necessary by the fact that the then_
and else_
sections of the if-then-else
statement are not necessarily executed. For what concerns the if_
section, they are triggered by the call to method finish_construction_and_register_if_task
of IfThenElseCaptureManager
at the end of the destructor of the temporary object of type _create_work_then_helper
(_create_work_else_helper
if the else_
section was specified) created by create_work_if(...).then_(...)
. Regarding the then_
and else_
sections, these actions are triggered by calling methods register_then_task
and register_else_task
of IfThenElseCaptureManager
at the end of the execution of the functor / lambda associated with the if
section.
A second method to create deferred work that is executed only if certain conditions are met is to use create_work_while
. This function is defined in darma/interface/app/create_work_while.h and its signature is:
template <typename Functor, typename... Args>
auto create_work_while(Args&& ... args)
The actions triggered by a call to create_work_while
are very similar to those initiated by create_work_if
. create_work_while
can be invoked using either a functor or a lambda: when a functor is used, its type should be specified as a template parameter of the function call. create_work_while
creates and returns an object of type _create_work_while_helper
, which is a struct specialized for functors and lambda that is defined in darma/impl/create_work/create_work_while_functor.h (for the functor case) and darma/impl/create_work/create_work_while_lambda.h (for the lambda case). The constructor of this object only stores the forwarded pack of arguments passed to create_work_while
in args_fwd_tup
, a std::tuple<...>
member variable. Similarly to _create_work_if_helper
, _create_work_while_helper
has a public method do_
that is used to specify the conditional deferred work to be performed should the while
clause be satisfied. This method creates an object of type _create_work_while_do_helper
, which is described in the next section.
_create_work_while_do_helper
is an helper struct whose functionalities are similar to those of _create_work_then_helper
: however, because there is never an additional clause as in the if-then-else
case, the responsibilities of creating the capture manager object, initiating the dependency capture process and registering the task with the runtime are now assigned to the struct constructor instead of the destructor. As mentioned in the previous section, _create_work_while_do_helper
creates an object of type WhileDoCaptureManager
and then calls its method register_while_task
. WhileDoCaptureManager
is described in details in the next section.
WhileDoCaptureManager
is again very similar to IfThenElseCaptureManager
. This struct is defined in darma/impl/create_work/create_work_while.h and is responsible for the dependency capture and tasks creation. The constructor of WhileDoCaptureManager
creates two objects of type WhileFunctorTask
and DoFunctorTask
that are stored in member variables. These objects, in turn, inherit from FunctorTask
(LambdaTask
for lambda callables), and their instantiation will trigger the dependency capture mechanism. Similarly to the create_work_if
case, dependency capture is performed in two steps: initially, the source and continuing access handles are stored in the std::map
member variables while_task_
and do_task_
, and their registration with the runtime is done only at a later time, just before the task is registered with the runtime. For the initial iteration of the while
construct, this is done through a call to method register_while_task
of WhileDoCaptureManager
. On subsequent iterations, the registration of both the do
and the following while
tasks are done within method run
of WhileFunctorTask
, which is invoked to execute the conditions to be checked in deciding whether to execute the deferred conditional work or not specified in the do
section of the construct.
Copyright © 2017 Sandia Corporation