-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RFC] task_group_dynamic_dependencies #1469
base: master
Are you sure you want to change the base?
Changes from all commits
18ad843
5404632
e1b964a
416d638
66710d2
1b32c34
aceddbd
19ac8d8
ff7e366
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,365 @@ | ||||||
# Extending task_group to manage dynamic dependencies between tasks | ||||||
|
||||||
## Introduction | ||||||
|
||||||
Back in 2021, during the move from TBB 2020 to the first release of oneTBB, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
the lowest level tasking interface changed significantly and was no longer | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
promoted as a user-facing feature. Instead, the guidance since then has been | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
to use the `task_group` or the flow graph APIs to express patterns that were | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
previously expressed using with the lowest level tasking API. And for most | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
cases, this has been sufficient. However, there is one use case which is not | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
straightforward to express by the revised API: Dynamic task graphs which are | ||||||
not trees. This proposal expands `tbb::task_group` to make additional use cases | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
easier to express. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
The class definition from section **[scheduler.task_group]** in the oneAPI | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. make it a hyperlink to the specs |
||||||
Threading Building Blocks (oneTBB) Specification 1.3-rev-1 for `tbb::task_group` | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
is shown below. Note the existing `defer` function because this function and | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
its return type, `task_handle`, are the foundation of our proposed extensions: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
class task_group { | ||||||
public: | ||||||
task_group(); | ||||||
task_group(task_group_context& context); | ||||||
|
||||||
~task_group(); | ||||||
|
||||||
template<typename Func> | ||||||
void run(Func&& f); | ||||||
|
||||||
template<typename Func> | ||||||
task_handle defer(Func&& f); | ||||||
vossmjp marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
void run(task_handle&& h); | ||||||
|
||||||
template<typename Func> | ||||||
task_group_status run_and_wait(const Func& f); | ||||||
|
||||||
task_group_status run_and_wait(task_handle&& h); | ||||||
|
||||||
task_group_status wait(); | ||||||
void cancel(); | ||||||
}; | ||||||
|
||||||
## Proposal | ||||||
|
||||||
The following table summarizes the three primary extensions that are under | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
consideration. The remainder of this post provides background and further | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
clarification on these proposed extensions. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
1. Extend semantics and useful lifetime of `task_handle`. We propose `task_handle` | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
to represent tasks for the purpose of adding dependencies. The useful lifetime and | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
semantics of `task_handle` will need to be extended to include tasks that have been | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
submitted, are currently executing, or have been completed. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
2. Add functions to set task dependencies. In the current `task_group`, tasks can | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
only be waited for as a group and there is no direct way to add any before-after | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
relationships between individual tasks. We will discuss options for spelling. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
3. Add a function to move successors from a currently executing task to a new task. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
This functionality is necessary for recursively generated task graphs. This case | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
represents a situation where it is safe to modify dependencies for an already | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
submitted task. | ||||||
|
||||||
### Extend the semantics and useful lifetime of task_handle | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
Dynamic tasks graphs order the execution of tasks via dependencies on the | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
completion of other tasks. They are dynamic because the creation of tasks, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
specification of dependencies between, submission of tasks for scheduling, and | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
execution of the tasks may happen concurrently and in various orders. Different | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
concrete use cases have different requirements on when new tasks are created | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
and when dependencies between tasks are specified. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
For the sake of discussion, let’s label four points in a task’s lifetime: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
1. created | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
2. submitted | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
3. executing | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
4. completed | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
A created task has been allocated but is not yet known to the scheduling | ||||||
algorithm and so cannot begin executing. A submitted task is known to the | ||||||
scheduling algorithm and whenever its incoming dependencies (predecessor tasks) | ||||||
are complete it may be scheduled for execution. An executing task has started | ||||||
executing its body but is not yet complete. Finally, a completed task has | ||||||
executed fully to completion. | ||||||
|
||||||
In the current specification for `task_group`, the function `task_group::defer` | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
already provides a mechanism to separate task creation from submission. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
`task_group::defer` returns a `tbb::task_handle`, which represents a created | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
task. A created task is in the created state until it is submitted via | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
the `task_group::run` or `task_group::run_and_wait` functions. In the current | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
specification of `task_group`, accessing a `task_handle` after it is submitted | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
via one of the run functions is undefined behavior. Currently, therefore, a | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
`task_handle` can only represent a created task. And currently, any task | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
that is run can immediately be scheduled for execution since there is no notion | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
of task dependencies for task_group. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
The first extension is to expand the semantics and usable lifetime of | ||||||
`task_handle` so that remains valid after it is passed to run and it can | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
represent tasks in any state, including submitted, executing, and completed | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
tasks. Similarly, a `task_handle` in the submitted state may represent a task | ||||||
Comment on lines
+96
to
+99
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it will be necessary to specify in more detail what that means for a task handle to be valid and represent a task in a certain state. As a C++ object, it is valid until it is destroyed, and nothing changes in this regard I guess. But what you can and cannot do with a task handle might depend on the current state of the associated task. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
that has predecessors that must complete before it can execute, and so passing | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
a `task_handle` to `task_group::run` or `task_group::run_and_wait` only makes | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
it available for dependency tracking, and does not make it immediately legal to | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
execute. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
### Add function(s) to set dependencies. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
The obvious next extension is to add a mechanism for specifying dependencies | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
between tasks. In the most conservative view, it should only be legal to add | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
additional predecessors / in-dependencies to tasks in the created state. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
After a task starts is completed, it doesn’t make sense to add additional | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
predecessors, since it’s too late for them to delay the start of the task’s | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
execution. | ||||||
|
||||||
It can make sense to add additional predecessors to a task that is | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
currently executing if the executing task is suspended until those | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
additional dependencies complete. However, in this proposal we do not intend | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
to support this suspension model. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
For a task in the submitted state, there can be a race between | ||||||
adding a new predecessor and the scheduler deciding to execute the task when its | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
currently known predecessors have completed. We will revisit the discussion of | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
adding predecessors to submitted tasks in the next section when we discuss | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
recursively grown task graphs. | ||||||
|
||||||
Having mostly settled the question about when a predecessors can be added, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
then next question is what can be added as a predecessor task? The most | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
user-friendly answer is to have no limitation; any valid `task_handle` can act as | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
a predecessor. In many cases, a developer may only know what work must be completed | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
before a task can start but does not know the state of that work. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
We therefore think predecessors may be in any state when they are added, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
as shown below: | ||||||
|
||||||
<img src="add_dependency.png" width=400> | ||||||
|
||||||
There are a number of possible options for the spelling of a function for adding | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
a single predecessor. We may also want a function to allow adding multiple | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
predecessors in one call. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
Given two `task_handle` objects `h1` and `h2`, some possible options | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
for adding `h1` as an in-dependence / predecessor of `h2` include: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
- `h2.add_predecessor(h1)` | ||||||
- `h2 = defer([]() { … }, h1)` | ||||||
- `make_edge(h1, h2)` | ||||||
|
||||||
We propose including the first option. Similarly, there could be | ||||||
Comment on lines
+140
to
+147
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would prefer not to add methods to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
versions of these two functions the accepted multiple predecessors | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
at once: | ||||||
|
||||||
- `h.add_predecessors(h1, ..., hn)` | ||||||
|
||||||
In the general case, it would be undefined behavior to add a new predecessor | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
to a task in the submitted, executing or completed states. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
### Add a function for recursively grown graphs | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
A very common use case for oneTBB tasks is parallel recursive decomposition. | ||||||
The implementation of tbb::parallel_for is an example of an algorithm that | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
performs a parallel recursive decomposition of the Range. We currently | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
implement the oneTBB algorithms, such as tbb::parallel_for, using the non-public, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
low-level tasking API, not tbb::task_group. The current low-level tasking API | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
puts all the burden on developers for both dependence tracking and memory | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
management of tasks. This lets the TBB development team build highly optimized | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
algorithms, but we believe a simpler set of interfaces are possible for TBB | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
users. Recursive parallel algorithms are one of the primary cases that we want | ||||||
our task_group extension to cover. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
The key capability required for recursive decomposition is the ability to | ||||||
create work while executing a task and insert this newly created work before | ||||||
the (perhaps already submitted) successors of the currently executing task. | ||||||
As a simple example, consider a merge sort. As shown in the figure that | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
follows, the top-level algorithm breaks a collection into two pieces and | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
creates three tasks: | ||||||
|
||||||
1. a task to sort the left half | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
2. a task to sort the right half | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
3. a task to merge the left and right sorted halves once they have been sorted. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
In a recursive merge sort, each of the sort tasks recursively takes the same | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
approach to sort their portions of the collection. The top-level task (and | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
subsequent recursively generated tasks) must be able to create new tasks | ||||||
and then update the graph so that their outer merge task waits for the | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
newly created subtasks to complete. | ||||||
|
||||||
<img src="merge_sort.png" width=800> | ||||||
|
||||||
A key point about this recursive parallel algorithm is that we must change | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
the predecessors of the merge tasks. But the merge tasks are already | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
submitted at the time their predecessors are modified! In the previous | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
section, we noted that updating the predecessors of a submitted task is | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
risky, because there is a potential race. However, in the example shown | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
here, we know it’s safe to add additional predecessors to the merge task | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
because it simply cannot start executing until all its current predecessors | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
complete, and its predecessors are the tasks modifying the predecessors! | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
We therefore propose a very limited extension that allows the transfer of | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
all the successors of a currently executing task to become the successors | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
of a different created task. This function can only access the successors | ||||||
of the currently executing task, and those tasks are prevented from executing | ||||||
by a dependence on the current task itself, so we can ensure that we can safely | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
update the incoming dependencies for those tasks without worrying about any | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
potential race. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
One possible spelling for this function would be `transfer_successors_to(h)`, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
where `h` is a `task_handle` to a created task and the | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
`transfer_successors_to` function must be called from within a task. Calling | ||||||
this function from outside a task, or passing anything other than a `task_handle` | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
representing a task in the created state is undefined behavior. | ||||||
|
||||||
### Proposed changes to task_handle | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
namespace oneapi { | ||||||
namespace tbb { | ||||||
class task_handle { | ||||||
public: | ||||||
|
||||||
// existing functions | ||||||
task_handle(); | ||||||
task_handle(task_handle&& src); | ||||||
~task_handle(); | ||||||
task_handle& operator=(task_handle&& th); | ||||||
explicit operator bool() const noexcept; | ||||||
|
||||||
// proposed additions | ||||||
void add_predecessor(task_handle& th); | ||||||
void add_successor(task_handle& th); | ||||||
Comment on lines
+226
to
+227
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we speak about the minimally required API, I doubt we need two variants of the same function, just with different name and the order of arguments. I would only keep one. |
||||||
}; | ||||||
|
||||||
void transfer_successors_to(task_handle& th); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this method is related to the currently executing task, what about including this API into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
} | ||||||
} | ||||||
|
||||||
|
||||||
#### void task_handle::add_predecessor(task_handle& th); | ||||||
|
||||||
Adds `th` as a predecessor that must complete before the task represented by | ||||||
`*this` can start executing. | ||||||
|
||||||
#### void task_handle::add_successor(task_handle& th); | ||||||
|
||||||
Adds `th` as a successor that cannot start executing until the task represented by | ||||||
`*this` is complete. | ||||||
|
||||||
#### void transfer_successors_to(task_handle& th); | ||||||
|
||||||
Transfers all of the successors from the currently executing task to the task | ||||||
represented by `th`. | ||||||
|
||||||
### Small examples | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
##### A simple three task graph | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
The example below shows a very simple graph with three nodes. The | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
`final_task` must wait for both the `first_task` and `second_task` | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
to complete. | ||||||
|
||||||
tbb::task_group tg; | ||||||
|
||||||
tbb::task_handle first_task = tg.defer([&] { /* task body */ }); | ||||||
tbb::task_handle second_task = tg.defer([&] { /* task body */ }); | ||||||
tbb::task_handle final_task = tg.defer([&] { /* task body */ }); | ||||||
|
||||||
final_task.add_predecessor(first_task); | ||||||
final_task.add_predecessor(second_task); | ||||||
// optionally: final_task.add_predecessors(first_task, second_task); | ||||||
|
||||||
// order of submission is not important | ||||||
tg.run(final_task); | ||||||
tg.run(first_task); | ||||||
tg.run(second_task); | ||||||
|
||||||
tg.wait(); | ||||||
|
||||||
The dependency graph for this example is: | ||||||
|
||||||
<img src="three_task_graph.png" width=400> | ||||||
|
||||||
#### Adding predecessors in unknown states | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
The example below shows a graph where the dependencies are determined | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
dynamically. The state of the predecessors may be unknown – they may | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
be created, submitted, executing or completed. Although not shown, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
let's assume that the user's `users::find_predecessors` function | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
returns, based on application logic, the tasks that must complete | ||||||
before the new work can start. | ||||||
|
||||||
void add_another_task(tbb::task_group& tg, int work_id) { | ||||||
tbb::task_handle new_task = tg.defer([=] { do_work(work_id); }); | ||||||
|
||||||
for (tbb::task_handle& p : users::find_predecessors(work_id)) { | ||||||
new_task.add_predecessor(p); | ||||||
} | ||||||
|
||||||
tg.run(new_task); | ||||||
} | ||||||
|
||||||
Again, the graph, as shown below, is simple. However, now we do not | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
know the completion status of the predecessors. Therefore, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
for ease-of-use, a `task_handle` should be usable as a dependency | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
regardless of state of the task it represents. Any predecessor | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
that is already completed when it is added as a predecessor will | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
not delay the start of the dependent task. Otherwise, end-users | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
will need to track these states explicitly. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
<img src="unknown_states.png" width=400> | ||||||
|
||||||
#### And example of recursive decomposition | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
This example is a version of merge-sort (with many of the details left out). | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
Assume that there is an initial task that executes the function shown | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
below as its body, and the function implements that task, and also serves | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
as the body for the recursively decomposed pieces. The beginning and | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
end of the sequence are represented by `b` and `e`, and much of the | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
(unimportant) details of the implementation of merge-sort is hidden | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
behind the functions `users::do_serial_sort`, `users::create_left_range`, | ||||||
`users::create_right_range`, and `users::do_merge`. | ||||||
|
||||||
template<typename T> | ||||||
void merge_sort(tbb::task_group& tg, T b, T e) { | ||||||
if (users::range_is_too_small(b, e)) { | ||||||
// base-case when range is small | ||||||
users::do_serial_sort(b, e); | ||||||
} else { | ||||||
// calculate left and right ranges | ||||||
T lb, le, rb, re; | ||||||
users::create_left_range(lb, le, b, e); | ||||||
users::create_right_range(rb, re, b, e); | ||||||
|
||||||
// create the three tasks | ||||||
tbb::task_handle sortleft = | ||||||
tg.defer([lb, le, &tg] { | ||||||
merge_sort(tg, lb, le); | ||||||
}); | ||||||
tbb::task_handle sortright = | ||||||
tg.defer([rb, re, &tg] { | ||||||
merge_sort(tg, rb, re); | ||||||
}); | ||||||
tbb::task_handle merge = | ||||||
tg.defer([rb, re, &tg] { | ||||||
users::do_merge(tg, lb, le, rb, re); | ||||||
}); | ||||||
|
||||||
// add predecessors for new merge task | ||||||
merge.add_predecessors(sortleft, sortright); | ||||||
|
||||||
// insert new subgraph between currently executing | ||||||
// task and its successors | ||||||
tbb::transfer_successors_to(merge); | ||||||
|
||||||
tg.run(sortleft); | ||||||
tg.run(sortright); | ||||||
tg.run(merge); | ||||||
} | ||||||
} | ||||||
|
||||||
The task tree for this example matches the one shown earlier for merge-sort. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
## Open Questions in Design | ||||||
|
||||||
Some open questions that remain: | ||||||
Comment on lines
+359
to
+361
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should there be more implementation-level questions added, such as feasibility of implementing the proposed design, performance implications, etc.? |
||||||
|
||||||
- Are the suggested APIs sufficient? | ||||||
- Are there additional use cases that should be considered that we missed in our analysis? | ||||||
- Are there other parts of the pre-oneTBB tasking API that developers have struggled to find a good alternative for? | ||||||
Comment on lines
+363
to
+365
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are interesting questions. While reading this RFC, I kind of rushed proposing additional syntax sugar that besides being more user-friendly, since it seems to represent popular use cases, can save some CPU cycles. So I am posting them here for a discussion.
template <typename Func>
task_handle transfer_successors_to(Func&& f); For the recursive decomposition scenario, instantiating a new task within an executing task and transferring successors to that new task seems to be the main model of writing such algorithms. Although, it seems to be not saving much (only one call to the library and assign to a couple of pointers?), there is always(?) going to be such a sequence in the code. Otherwise, how else an execution of already submitted tasks can be postponed? Shall we also consider a question of having that API instead or in addition to the one proposed?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should start with minimal API then extend with syntactic sugar based on use cases. I suspect this will start as an experimental feature to allow some feedback on API. Even so, I'm also open to including these additional APIs in the initial implementation, if others think they're likely needed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.