Skip to content
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

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

vossmjp
Copy link
Contributor

@vossmjp vossmjp commented Aug 5, 2024

Description

A proposal to extend task_group:

  1. Extend semantics and useful lifetime of task_handle. We propose task_handle to represent tasks for the purpose of adding dependencies. The useful lifetime and semantics of task_handle will need to be extended to include tasks that have been submitted, are currently executing, or have been completed.
  2. Add functions to set task dependencies. In the current 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.
  3. Add a function to move successors from a currently executing task to a new task. This functionality is necessary for recursively generated task graphs. This case represents a situation where it is safe to modify dependencies for an already submitted task.

Type of change

  • bug fix - change that fixes an issue
  • new feature - change that adds functionality
  • tests - change in tests
  • infrastructure - change in infrastructure and CI
  • documentation - documentation update

Tests

  • added - required for new features and some bug fixes
  • not needed

Documentation

  • updated in # - add PR number
  • needs to be updated
  • not needed

Breaks backward compatibility

  • Yes
  • No
  • Unknown

Notify the following users

List users with @ to send notifications

Other information

@pavelkumbrasev
Copy link
Contributor

I think this proposal is lacking the final definition of class task_handle including all the new methods.

Base automatically changed from dev/vossmjp/rfcs to master September 26, 2024 14:02
rfcs/proposed/task_group_dynamic_dependencies/README.md Outdated Show resolved Hide resolved
rfcs/proposed/task_group_dynamic_dependencies/README.md Outdated Show resolved Hide resolved
rfcs/proposed/task_group_dynamic_dependencies/README.md Outdated Show resolved Hide resolved
void add_successor(task_handle& th);
};

void transfer_successors_to(task_handle& th);
Copy link
Contributor

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.

Copy link
Contributor

@akukanov akukanov Dec 5, 2024

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.

rfcs/proposed/task_group_dynamic_dependencies/README.md Outdated Show resolved Hide resolved
Comment on lines +363 to +365
- 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?
Copy link
Contributor

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.

  1. 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?

  1. As for add_predecessor(s) and add_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:
        template <typename Func>
        void add_predecessor(Func&& f);
    
        template <typename Func>
        void add_successor(Func&& f);
    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.
    So here I suggest to have something like:
        template <typename... Func>
        void add_predecessors(Func&& ...f);
    
        template <typename... Func>
        void add_successor(Func&& ...f);
    However, this also seems to ask for having the 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...

Copy link
Contributor Author

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.

@vossmjp vossmjp force-pushed the dev/vossmjp/rfc_task_group_dynamic_dependencies branch from 579bcd8 to ff7e366 Compare December 4, 2024 23:37
@vossmjp vossmjp marked this pull request as ready for review December 4, 2024 23:40
Comment on lines +226 to +227
void add_predecessor(task_handle& th);
void add_successor(task_handle& th);
Copy link
Contributor

@akukanov akukanov Dec 5, 2024

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.

Comment on lines +96 to +99
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
Copy link
Contributor

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.

Comment on lines +140 to +147
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
Copy link
Contributor

@akukanov akukanov Dec 5, 2024

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.

Comment on lines +359 to +361
## Open Questions in Design

Some open questions that remain:
Copy link
Contributor

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# 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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants