-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Documentation for Task Group Dynamic Dependencies #1863
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
base: master
Are you sure you want to change the base?
Changes from 11 commits
74a8a22
5c1ee1c
5252568
2c83040
c973e8e
7fc8ba2
0a8aa07
0b81e65
8012e8a
e642c63
f864a64
0a77400
7f4d219
b70a323
f363917
e513f87
d49ae72
73d721a
63112a4
5f40d8e
d5a0fa7
0845311
19d54d6
0027f75
95eac21
7768cac
a3a0500
3796fb9
73855db
59b4dc0
6d8a33b
a7d90c8
3e44174
33e1b16
3cfe4e9
3715c6f
a8e9580
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,82 @@ | ||
/* | ||
Copyright (c) 2025 Intel Corporation | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
#include <cstdint> | ||
#include <vector> | ||
|
||
constexpr std::size_t N = 10000; | ||
|
||
std::vector<std::size_t> global_vector(N, 0); | ||
|
||
void foo(std::size_t begin, std::size_t end) { | ||
for (std::size_t i = begin; i < end; ++i) { | ||
global_vector[i] = 42; | ||
} | ||
} | ||
|
||
/*begin_task_group_extensions_bypassing_example*/ | ||
#define TBB_PREVIEW_TASK_GROUP_EXTENSIONS 1 | ||
#include "oneapi/tbb/task_group.h" | ||
|
||
void foo(std::size_t begin, std::size_t end); | ||
|
||
struct for_task { | ||
static constexpr std::size_t serial_threshold = 16; | ||
tbb::task_handle operator()() const { | ||
tbb::task_handle next_task; | ||
std::size_t size = end - begin; | ||
if (size < serial_threshold) { | ||
// Execute the work serially | ||
foo(begin, end); | ||
} else { | ||
// Processed range to big - split it | ||
std::size_t middle = begin + size / 2; | ||
tbb::task_handle left_subtask = tg.defer(for_task{begin, middle, tg}); | ||
tbb::task_handle right_subtask = tg.defer(for_task{middle, end, tg}); | ||
|
||
// Submit the right subtask for execution | ||
tg.run(for_task{middle, end, tg}); | ||
|
||
// Bypass the left part | ||
next_task = tg.defer(for_task{begin, middle, tg}); | ||
} | ||
return next_task; | ||
} | ||
|
||
std::size_t begin; | ||
std::size_t end; | ||
tbb::task_group& tg; | ||
}; // struct for_task | ||
|
||
void calculate_parallel_for(std::vector<std::size_t>& vec) { | ||
tbb::task_group tg; | ||
// Run the root task | ||
tg.run_and_wait(for_task{0, vec.size(), tg}); | ||
} | ||
akukanov marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
/*end_task_group_extensions_bypassing_example*/ | ||
|
||
#include <iostream> | ||
|
||
int main() { | ||
calculate_parallel_for(global_vector); | ||
|
||
for (std::size_t i = 0; i < global_vector.size(); ++i) { | ||
if (global_vector[i] != 42) { | ||
std::cerr << "Error in " << i << " index" << std::endl; | ||
return 1; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
/* | ||
Copyright (c) 2025 Intel Corporation | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
#include <cstdint> | ||
|
||
/*begin_task_group_extensions_reduction_example*/ | ||
|
||
#define TBB_PREVIEW_TASK_GROUP_EXTENSIONS 1 | ||
#include "oneapi/tbb/task_group.h" | ||
kboyarinov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
struct reduce_task { | ||
static constexpr std::size_t serial_threshold = 16; | ||
akukanov marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
struct join_task { | ||
void operator()() const { | ||
*result = *left + *right; | ||
} | ||
|
||
std::shared_ptr<std::size_t> left; | ||
std::shared_ptr<std::size_t> right; | ||
std::shared_ptr<std::size_t> result; | ||
}; | ||
|
||
tbb::task_handle operator()() const { | ||
tbb::task_handle next_task; | ||
|
||
std::size_t size = end - begin; | ||
if (size < serial_threshold) { | ||
// Perform serial reduction | ||
for (std::size_t i = begin; i < end; ++i) { | ||
*result += i; | ||
} | ||
} else { | ||
// The range is too large to process directly | ||
// Divide it into smaller segments for parallel execution | ||
std::size_t middle = begin + size / 2; | ||
|
||
std::shared_ptr<std::size_t> left_result = std::make_shared<std::size_t>(0); | ||
tbb::task_handle left_leaf = tg.defer(reduce_task{begin, middle, left_result, tg}); | ||
|
||
std::shared_ptr<std::size_t> right_result = std::make_shared<std::size_t>(0); | ||
tbb::task_handle right_leaf = tg.defer(reduce_task{middle, end, right_result, tg}); | ||
|
||
tbb::task_handle join = tg.defer(join_task{left_result, right_result, result}); | ||
akukanov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
tbb::task_group::set_task_order(left_leaf, join); | ||
tbb::task_group::set_task_order(right_leaf, join); | ||
|
||
tbb::task_group::transfer_this_task_completion_to(join); | ||
|
||
// Save the left leaf for further bypassing | ||
next_task = std::move(left_leaf); | ||
|
||
tg.run(std::move(right_leaf)); | ||
tg.run(std::move(join)); | ||
} | ||
|
||
return next_task; | ||
} | ||
|
||
std::size_t begin; | ||
std::size_t end; | ||
std::shared_ptr<std::size_t> result; | ||
tbb::task_group& tg; | ||
}; | ||
|
||
std::size_t calculate_parallel_sum(std::size_t begin, std::size_t end) { | ||
tbb::task_group tg; | ||
|
||
std::shared_ptr<std::size_t> reduce_result = std::make_shared<std::size_t>(0); | ||
reduce_task root_reduce_task{begin, end, reduce_result, tg}; | ||
tg.run_and_wait(root_reduce_task); | ||
|
||
return *reduce_result; | ||
} | ||
|
||
/*end_task_group_extensions_reduction_example*/ | ||
kboyarinov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#include <iostream> | ||
|
||
int main() { | ||
constexpr std::size_t N = 10000; | ||
std::size_t serial_sum = N * (N - 1) / 2; | ||
std::size_t parallel_sum = calculate_parallel_sum(0, N); | ||
|
||
if (serial_sum != parallel_sum) std::cerr << "Incorrect reduction result" << std::endl; | ||
} |
akukanov marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
.. _task_group_bypass_support: | ||
|
||
Task Bypass Support for ``task_group`` | ||
====================================== | ||
|
||
.. note:: | ||
To enable this extension, define the ``TBB_PREVIEW_TASK_GROUP_EXTENSIONS`` macro with a value of ``1``. | ||
|
||
.. contents:: | ||
:local: | ||
:depth: 2 | ||
|
||
Description | ||
*********** | ||
|
||
The |full_name| implementation extends the requirements for user-provided function object from | ||
`tbb::task_group specification <https://oneapi-spec.uxlfoundation.org/specifications/oneapi/latest/elements/onetbb/source/task_scheduler/task_group/task_group_cls>`_ | ||
to allow them to return a ``task_handle`` object. | ||
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. It seems that task_group specification is silent on the return type of the user-provided function object. So "extends the requirements" doesn't seem right. 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. As far as I understand the idea of named requirement and the return type is that if the specification says nothing about the return type means that any type can be returned, but will be ignored. So "extending" the named requirement means that the implementation will react if the handle is returned. |
||
|
||
`Task Bypassing <../tbb_userguide/Task_Scheduler_Bypass.html>`_ allows developers to reduce task scheduling overhead by providing a hint about | ||
which task should be executed next. | ||
|
||
Execution of the hinted task is not guaranteed to occur immediately, nor to be performed by the same thread. | ||
kboyarinov marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
.. code:: cpp | ||
|
||
tbb::task_handle task_body() { | ||
tbb::task_handle next_task = group.defer(next_task_body); | ||
return next_task; | ||
} | ||
|
||
API | ||
*** | ||
|
||
Header | ||
------ | ||
|
||
.. code:: cpp | ||
|
||
#define TBB_PREVIEW_TASK_GROUP_EXTENSIONS 1 | ||
#include <oneapi/tbb/task_group.h> | ||
|
||
Synopsis | ||
-------- | ||
|
||
.. code:: cpp | ||
|
||
namespace oneapi { | ||
namespace tbb { | ||
class task_group { | ||
public: | ||
// Only the requirements for the return type of function F are changed | ||
template <typename F> | ||
task_handle defer(F&& f); | ||
|
||
// Only the requirements for the return type of function F are changed | ||
template <typename F> | ||
task_group_status run_and_wait(const F& f); | ||
|
||
// Only the requirements for the return type of function F are changed | ||
template <typename F> | ||
void run(F&& f); | ||
}; // class task_group | ||
} // namespace tbb | ||
} // namespace oneapi | ||
|
||
Member Functions | ||
---------------- | ||
|
||
.. code:: cpp | ||
|
||
template <typename F> | ||
task_handle defer(F&& f); | ||
|
||
template <typename F> | ||
task_group_status run_and_wait(const F& f); | ||
|
||
template <typename F> | ||
void run(F&& f); | ||
|
||
The function object ``F`` may return a ``task_handle`` object. If the returned handle is non-empty and owns a task without dependencies, it serves as an optimization | ||
hint for a task that could be executed next. | ||
|
||
If the returned handle was created by a ``task_group`` other than ``*this``, the behavior is undefined. | ||
|
||
Example | ||
------- | ||
|
||
The example below demonstrates how to implement a parallel for loop using ``task_group`` and divide-and-conquer pattern. | ||
kboyarinov marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
.. literalinclude:: ./examples/task_group_extensions_bypassing.cpp | ||
:language: c++ | ||
:start-after: /*begin_task_group_extensions_bypassing_example*/ | ||
:end-before: /*end_task_group_extensions_bypassing_example*/ |
Uh oh!
There was an error while loading. Please reload this page.