|
Peano
|
Very simple dynamic task graph interface. More...
#include <dynamic_task_graph.h>

Public Member Functions | |
| dynamic_task_graph (::oneapi::tbb::task_group &arena) | |
| Construct task graph over arena. | |
| void | put (dynamic_task_graph_node &node) |
| Put a new node into the task graph. | |
| void | wait (dynamic_task_graph_node node) |
| Wait for one specific node. | |
| void | wait (const std::set< dynamic_task_graph_node > &node) |
| Wait for set of nodes. | |
| void | wait_for_all () |
| Wait for all tasks to finish. | |
| std::size_t | size () const |
| Number of tasks in graph. | |
Private Types | |
| using | SpawnedTaskMutex = ::oneapi::tbb::spin_rw_mutex |
| I originally used. | |
| using | SpawnedTasksContainer = std::set< std::shared_ptr< dynamic_task_graph_spawned_node > > |
| See documentation of dynamic_task_graph_spawned_node for a discussion why we have to use smart pointers here. | |
Private Member Functions | |
| void | clean_up_completed_tasks () |
| Remove all completed task. | |
Private Attributes | |
| ::oneapi::tbb::task_group & | _task_group |
| SpawnedTaskMutex | _spawned_task_mutex |
| SpawnedTasksContainer | _spawned_tasks |
| Spawned tasks known to system. | |
Friends | |
| class | dynamic_task_graph_spawned_node |
Very simple dynamic task graph interface.
Each task graph is tied to an arena and maintains a set of graphs nodes aka tasks with dependencies between them. Immediately after tasks become ready, the task graph spawns them into the arena.
The task graph holds a big hash map mapping individual task numbers of type dynamic_task_graph_node_number onto dynamic_task_graph_node which in turn hold the actual task graph. The entries are added to the list when we construct a node. They are removed after the ask has terminated.
Definition at line 138 of file dynamic_task_graph.h.
|
private |
I originally used.
but it is absolutely crucial that the lock for the spawned tasks does not become a bottleneck.
Definition at line 294 of file dynamic_task_graph.h.
|
private |
See documentation of dynamic_task_graph_spawned_node for a discussion why we have to use smart pointers here.
Just to reiterate the core message why we need this: A user creates an instance of a task, i.e. dynamic_task_graph_node. Once submitted, this node holds a (smart) pointer to a task, and then is allowed to throw the node away. So we have to safe the submitted task!
Definition at line 307 of file dynamic_task_graph.h.
| tbb::dynamic_task_graph::dynamic_task_graph | ( | ::oneapi::tbb::task_group & | arena | ) |
Construct task graph over arena.
| arena | Arena to be used once the task becomes ready. It is the user's responsibility to ensure that the arena is valid. |
|
private |
Remove all completed task.
Almost a garbage collection over the tasks that are complete. This makes follow up traversals of the _spawned_tasks container faster. The routine is a helper for the waits.
| void tbb::dynamic_task_graph::put | ( | dynamic_task_graph_node & | node | ) |
Put a new node into the task graph.
The ownership of the node is passed over to the task graph. You don't have to delete it yourself. This is different to oneAPI's flow graph, where we can work with references, as we rely on the fact that nodes stay alive until we call wait_for_all. Here, everything is dynamic and the task might or might not be executed immediately or be referenced later on.
Once you have submitted a node, it becomes read-only, i.e. you cannot add any further in-dependencies to it. But you can still copy it or you can even delete the object. The ownership of the underlying "real" task now resides within the dynamic_task_graph object.
Internally, the put will create a second object of the type dynamic_task_graph_spawned_node, and then it will translate all dependencies into links between instances of dynamic_task_graph_spawned_node. However, the directions here are inverted: Users model their task dependencies as in-dependencies. When they submit tasks, the relations are translated into out-dependencies, i.e. each task knows which tasks are follow-up tasks and need to be notified once we are ready. We translate in-dependencies into a forward-flow model.
As everything is dynamic here, it could be that we refer with an in-dependency to a task that's already completed. It might even have been removed completely from the set of spawned tasks. In this case, we can safely ignore these in-dependency. We do not even have to increment the dependency counter.
We always have to increment the task dependency counter before we actually insert the new task, as the predecessor migth literally just now be about to finish and hence immediately unlock our own task while we still construct its input dependencies. By incrementing first, we are on the safe side that our task will not gallop ahead.
| std::size_t tbb::dynamic_task_graph::size | ( | ) | const |
Number of tasks in graph.
This includes all running and pending tasks.
Referenced by tarch.plotter.api.input.Patch.Patch::__repr__().

| void tbb::dynamic_task_graph::wait | ( | const std::set< dynamic_task_graph_node > & | node | ) |
Wait for set of nodes.
| void tbb::dynamic_task_graph::wait | ( | dynamic_task_graph_node | node | ) |
Wait for one specific node.
Very simplistic implementation: We know that the task has been submitted, so its spawned task's pointer should be in the repo. If it is not there (anymore), it has finished. It also could still be in there yet have the state complete. So we run into a while loop over the repo. As long as the task is still bookmarked there, we yield.
I originally had a wait in there on the underlying task graph:
This cannot work if we study the TBB documentation (https://oneapi-spec.uxlfoundation.org/specifications/oneapi/v1.3-rev-1/elements/onetbb/source/task_scheduler/task_group/task_group_cls): wait() returns if and only if all tasks in the group have completed (or we cancelled them). So we should use a taskyield() indeed.
| node | Task to wait for. This task has to be submitted. Otherwise, we trigger an assertion. |
| void tbb::dynamic_task_graph::wait_for_all | ( | ) |
Wait for all tasks to finish.
This is a brute force method which has to be used with care. It strongly synchronises, and it is not clear if it can deadlock if multiple threads invoke it at the same time.
The implementation is close to trivial:
It seems that wait() on a task group returns immediately if there are no further tasks pending in there. This is very reasonable yet might mean that there are still tasks being processed at this point. They might in return not have released any successor tasks, i.e. there might still be some tasks in the queue that are marked as submitted and have not yet been released as their in dependency counter has not been decreased yet. Therefore, we have to loop.
|
friend |
Definition at line 140 of file dynamic_task_graph.h.
|
private |
Definition at line 295 of file dynamic_task_graph.h.
|
private |
Spawned tasks known to system.
Definition at line 312 of file dynamic_task_graph.h.
|
private |
Definition at line 282 of file dynamic_task_graph.h.