-
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?
Conversation
I think this proposal is lacking the final definition of |
void add_successor(task_handle& th); | ||
}; | ||
|
||
void transfer_successors_to(task_handle& th); |
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.
Since this method is related to the currently executing task, what about including this API into tbb::this_task::
namespace? By analogy with tbb::this_task_arena::
namespace.
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.
I would use tbb::task
namespace, since it is already used for suspend
(which applies to the currently running task) and resume
functions.
Actually no, I think it should not be in the namespace task
or this_task
or just tbb
, but rather it should be a static function or a member function in task_group
. The reason is that, since task_group::defer
is the only way to create a new non-empty task_handle
, the method to transfer successors cannot be used in arbitrary tasks, only in task_group
tasks.
- 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? |
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.
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.
- Would it be useful to have a method that simultaneously joins (or even fuses?) the instantiation of a task and transferring of the current task successors?
Something like:
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?
- As for
add_predecessor(s)
andadd_successor(s)
I have a couple of thoughts.
a. It seems again that it might be useful to merge instantiation of the new task handles and adding them as successors/predecessors:b. Also, I think having an API that would allow adding more than one predecessor/successor at once can be useful, since usually a number of successors/predecessors are instantiated. I only think that we don't need to limit ourselves to only two parameters as it was proposed optionally, but allow passing of an arbitrary size of task handles or even user lambdas. Of course, a pattern of having a single task producer might be viewed as a limiting one, but there actually might be the cases where tasks cannot be made available to the scheduler (i.e. spawned) until all of them are gathered together from different producers, which essentially represents a barrier in the execution pipeline. Not to mention that the spawning of a bunch of tasks all together are done faster than regular one by one spawnings. Spawning of a bunch of tasks at once was implemented in the old TBB, as far as I remember.template <typename Func> void add_predecessor(Func&& f); template <typename Func> void add_successor(Func&& f);
So here I suggest to have something like:However, this also seems to ask for having thetemplate <typename... Func> void add_predecessors(Func&& ...f); template <typename... Func> void add_successor(Func&& ...f);
task_group::run()
method to accept an arbitrary size of task handles and/or functors. So, perhaps, it is more related to another RFC/extension...
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.
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.
Co-authored-by: Aleksei Fedotov <[email protected]>
Co-authored-by: Aleksei Fedotov <[email protected]>
Co-authored-by: Aleksei Fedotov <[email protected]>
Co-authored-by: Aleksei Fedotov <[email protected]>
579bcd8
to
ff7e366
Compare
void add_predecessor(task_handle& th); | ||
void add_successor(task_handle& th); |
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.
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.
See also another comment about functions vs. class methods.
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 | ||
represent tasks in any state, including submitted, executing, and completed | ||
tasks. Similarly, a `task_handle` in the submitted state may represent a task |
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.
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.
Given two `task_handle` objects `h1` and `h2`, some possible options | ||
for adding `h1` as an in-dependence / predecessor of `h2` include: | ||
|
||
- `h2.add_predecessor(h1)` | ||
- `h2 = defer([]() { … }, h1)` | ||
- `make_edge(h1, h2)` | ||
|
||
We propose including the first option. Similarly, there could be |
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.
I would prefer not to add methods to task_handle
but use "external" functions, perhaps in the task_group
class. This would be more consistent with the current approach (defer
, run
, run_and_wait
) as well as with transfer_successors_to
.
## Open Questions in Design | ||
|
||
Some open questions that remain: |
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.
Should there be more implementation-level questions added, such as feasibility of implementing the proposed design, performance implications, etc.?
@@ -0,0 +1,365 @@ | |||
# Extending task_group to manage dynamic dependencies between tasks |
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.
# Extending task_group to manage dynamic dependencies between tasks | |
# Extend ``task_group`` for Dynamic Task Dependencies |
|
||
## 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Back in 2021, during the move from TBB 2020 to the first release of oneTBB, | |
In 2021, with the trasition from TBB 2020 to the first release of oneTBB, |
## Introduction | ||
|
||
Back in 2021, during the move from TBB 2020 to the first release of oneTBB, | ||
the lowest level tasking interface changed significantly and was no longer |
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.
the lowest level tasking interface changed significantly and was no longer | |
the lowest-level tasking interface changed significantly and was no longer |
|
||
Back in 2021, during the move from TBB 2020 to the first release of oneTBB, | ||
the lowest level tasking interface changed significantly and was no longer | ||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
promoted as a user-facing feature. Instead, the guidance since then has been | |
promoted as a user-facing feature. Instead, we encouraged |
Back in 2021, during the move from TBB 2020 to the first release of oneTBB, | ||
the lowest level tasking interface changed significantly and was no longer | ||
promoted as a user-facing feature. Instead, the guidance since then has been | ||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
to use the `task_group` or the flow graph APIs to express patterns that were | |
to use the `task_group` or the flow graph APIs to express patterns |
|
||
This example is a version of merge-sort (with many of the details left out). | ||
Assume that there is an initial task that executes the function shown | ||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
below as its body, and the function implements that task, and also serves | |
This function implements that task and serves |
This example is a version of merge-sort (with many of the details left out). | ||
Assume that there is an initial task that executes the function shown | ||
below as its body, and the function implements that task, and also serves | ||
as the body for the recursively decomposed pieces. The beginning and |
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.
as the body for the recursively decomposed pieces. The beginning and | |
as the body for the recursively decomposed pieces. |
Assume that there is an initial task that executes the function shown | ||
below as its body, and the function implements that task, and also serves | ||
as the body for the recursively decomposed pieces. The beginning and | ||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
end of the sequence are represented by `b` and `e`, and much of the | |
The range of the sequence is defined by `b` (beginning) and `e` (end). |
below as its body, and the function implements that task, and also serves | ||
as the body for the recursively decomposed pieces. The beginning and | ||
end of the sequence are represented by `b` and `e`, and much of the | ||
(unimportant) details of the implementation of merge-sort is hidden |
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.
(unimportant) details of the implementation of merge-sort is hidden | |
Most of the implementation details of merge sort are abstracted into the following helper functions: |
} | ||
} | ||
|
||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
The task tree for this example matches the one shown earlier for merge-sort. | |
This task tree matches the one shown earlier for merge-sort. |
Description
A proposal to extend task_group:
task_handle
. We proposetask_handle
to represent tasks for the purpose of adding dependencies. The useful lifetime and semantics oftask_handle
will need to be extended to include tasks that have been submitted, are currently executing, or have been completed.task_group
, tasks can only be waited for as a group and there is no direct way to add any before-after relationships between individual tasks. We will discuss options for spelling.Type of change
Tests
Documentation
Breaks backward compatibility
Notify the following users
List users with
@
to send notificationsOther information