Skip to content

Commit f07d4df

Browse files
kboyarinovvossmjpakukanovisaevil
authored
Documentation for Task Group Dynamic Dependencies (#1863)
Co-authored-by: Mike Voss <[email protected]> Co-authored-by: Alexey Kukanov <[email protected]> Co-authored-by: Ilya Isaev <[email protected]>
1 parent d43b8c3 commit f07d4df

File tree

5 files changed

+663
-73
lines changed

5 files changed

+663
-73
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
Copyright (c) 2025 UXL Foundation Contributors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#include <cstdint>
18+
#include <vector>
19+
#include <iostream>
20+
21+
static constexpr std::size_t serial_threshold = 16;
22+
23+
/*begin_task_group_extensions_bypassing_example*/
24+
#define TBB_PREVIEW_TASK_GROUP_EXTENSIONS 1
25+
#include "oneapi/tbb/task_group.h"
26+
27+
template <typename Iterator, typename Function>
28+
struct for_task {
29+
tbb::task_handle operator()() const {
30+
tbb::task_handle next_task;
31+
32+
auto size = end - begin;
33+
if (size < serial_threshold) {
34+
// Execute the work serially
35+
for (Iterator it = begin; it != end; ++it) {
36+
f(*it);
37+
}
38+
} else {
39+
// Enough work to split the range
40+
Iterator middle = begin + size / 2;
41+
42+
// Submit the right subtask for execution
43+
tg.run(for_task<Iterator, Function>{middle, end, f, tg});
44+
45+
// Bypass the left subtask
46+
next_task = tg.defer(for_task<Iterator, Function>{begin, middle, f, tg});
47+
}
48+
return next_task;
49+
}
50+
51+
Iterator begin;
52+
Iterator end;
53+
Function f;
54+
tbb::task_group& tg;
55+
}; // struct for_task
56+
57+
template <typename RandomAccessIterator, typename Function>
58+
void par_for_each(RandomAccessIterator begin, RandomAccessIterator end, Function f) {
59+
tbb::task_group tg;
60+
// Run the root task
61+
tg.run_and_wait(for_task<RandomAccessIterator, Function>{begin, end, std::move(f), tg});
62+
}
63+
/*end_task_group_extensions_bypassing_example*/
64+
65+
int main() {
66+
constexpr std::size_t N = 1000;
67+
68+
std::size_t array[N];
69+
70+
par_for_each(array, array + N, [](std::size_t& item) {
71+
item = 42;
72+
});
73+
74+
for (std::size_t i = 0; i < N; ++i) {
75+
if (array[i] != 42) {
76+
std::cerr << "Error in " << i << "index" << std::endl;
77+
return 1;
78+
}
79+
}
80+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
Copyright (c) 2025 UXL Foundation Contributors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#include <cstdint>
18+
#include <iostream>
19+
20+
static constexpr std::size_t serial_threshold = 16;
21+
22+
/*begin_task_group_extensions_reduction_example*/
23+
#define TBB_PREVIEW_TASK_GROUP_EXTENSIONS 1
24+
#include "oneapi/tbb/task_group.h"
25+
26+
struct reduce_task {
27+
28+
struct join_task {
29+
void operator()() const {
30+
result = *left + *right;
31+
}
32+
33+
std::size_t& result;
34+
std::unique_ptr<std::size_t> left;
35+
std::unique_ptr<std::size_t> right;
36+
};
37+
38+
tbb::task_handle operator()() const {
39+
tbb::task_handle next_task;
40+
41+
std::size_t size = end - begin;
42+
if (size < serial_threshold) {
43+
// Perform serial reduction
44+
for (std::size_t i = begin; i < end; ++i) {
45+
result += i;
46+
}
47+
} else {
48+
// The range is too large to process directly
49+
// Divide it into smaller segments for parallel execution
50+
std::size_t middle = begin + size / 2;
51+
52+
auto left_result = std::make_unique<std::size_t>(0);
53+
auto right_result = std::make_unique<std::size_t>(0);
54+
55+
56+
tbb::task_handle left_leaf = tg.defer(reduce_task{begin, middle, *left_result, tg});
57+
tbb::task_handle right_leaf = tg.defer(reduce_task{middle, end, *right_result, tg});
58+
59+
tbb::task_handle join = tg.defer(join_task{result, std::move(left_result), std::move(right_result)});
60+
61+
tbb::task_group::set_task_order(left_leaf, join);
62+
tbb::task_group::set_task_order(right_leaf, join);
63+
64+
tbb::task_group::transfer_this_task_completion_to(join);
65+
66+
// Save the left leaf for further bypassing
67+
next_task = std::move(left_leaf);
68+
69+
tg.run(std::move(right_leaf));
70+
tg.run(std::move(join));
71+
}
72+
73+
return next_task;
74+
}
75+
76+
std::size_t begin;
77+
std::size_t end;
78+
std::size_t& result;
79+
tbb::task_group& tg;
80+
};
81+
82+
std::size_t calculate_parallel_sum(std::size_t begin, std::size_t end) {
83+
tbb::task_group tg;
84+
85+
std::size_t reduce_result = 0;
86+
tg.run_and_wait(reduce_task{begin, end, reduce_result, tg});
87+
88+
return reduce_result;
89+
}
90+
/*end_task_group_extensions_reduction_example*/
91+
92+
int main() {
93+
constexpr std::size_t N = 10000;
94+
std::size_t serial_sum = N * (N - 1) / 2;
95+
std::size_t parallel_sum = calculate_parallel_sum(0, N);
96+
97+
if (serial_sum != parallel_sum) std::cerr << "Incorrect reduction result" << std::endl;
98+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
.. _task_group_bypass_support:
2+
3+
Task Bypass Support for ``task_group``
4+
======================================
5+
6+
.. note::
7+
To enable this extension, define the ``TBB_PREVIEW_TASK_GROUP_EXTENSIONS`` macro with a value of ``1``.
8+
9+
.. contents::
10+
:local:
11+
:depth: 2
12+
13+
Description
14+
***********
15+
16+
The |full_name| implementation extends the requirements for user-provided function object from
17+
`tbb::task_group specification <https://oneapi-spec.uxlfoundation.org/specifications/oneapi/latest/elements/onetbb/source/task_scheduler/task_group/task_group_cls>`_
18+
to allow them to return a ``task_handle`` object.
19+
20+
`Task Bypassing <../tbb_userguide/Task_Scheduler_Bypass.html>`_ allows developers to reduce task scheduling overhead by providing a hint about
21+
which task should be executed next.
22+
23+
Execution of the deferred task owned by a returned ``task_handle`` is not guaranteed to occur immediately, nor to be performed by the same thread.
24+
25+
.. code:: cpp
26+
27+
tbb::task_handle task_body() {
28+
tbb::task_handle next_task = group.defer(next_task_body);
29+
return next_task;
30+
}
31+
32+
API
33+
***
34+
35+
Header
36+
------
37+
38+
.. code:: cpp
39+
40+
#define TBB_PREVIEW_TASK_GROUP_EXTENSIONS 1
41+
#include <oneapi/tbb/task_group.h>
42+
43+
Synopsis
44+
--------
45+
46+
.. code:: cpp
47+
48+
namespace oneapi {
49+
namespace tbb {
50+
class task_group {
51+
public:
52+
// Only the requirements for the return type of function F are changed
53+
template <typename F>
54+
task_handle defer(F&& f);
55+
56+
// Only the requirements for the return type of function F are changed
57+
template <typename F>
58+
task_group_status run_and_wait(const F& f);
59+
60+
// Only the requirements for the return type of function F are changed
61+
template <typename F>
62+
void run(F&& f);
63+
}; // class task_group
64+
} // namespace tbb
65+
} // namespace oneapi
66+
67+
Member Functions
68+
----------------
69+
70+
.. code:: cpp
71+
72+
template <typename F>
73+
task_handle defer(F&& f);
74+
75+
template <typename F>
76+
task_group_status run_and_wait(const F& f);
77+
78+
template <typename F>
79+
void run(F&& f);
80+
81+
The ``F`` type must meet the *Function Objects* requirements described in the [function.objects] section of the ISO C++ Standard.
82+
83+
.. admonition:: Extension
84+
85+
``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
86+
for a task that could be executed next.
87+
88+
The returned ``task_handle`` must not be explicitly submitted with ``task_group::run`` or another submission function, otherwise, the behavior is undefined.
89+
90+
If the returned handle was created by a ``task_group`` other than ``*this``, the behavior is undefined.
91+
92+
Example
93+
-------
94+
95+
The example below demonstrates how to process a sequence in parallel using ``task_group`` and the divide-and-conquer pattern.
96+
97+
.. literalinclude:: ./examples/task_group_extensions_bypassing.cpp
98+
:language: c++
99+
:start-after: /*begin_task_group_extensions_bypassing_example*/
100+
:end-before: /*end_task_group_extensions_bypassing_example*/

0 commit comments

Comments
 (0)