Task Bypass Support for task_group

Task Bypass Support for task_group#

Note

To enable this extension, define the TBB_PREVIEW_TASK_GROUP_EXTENSIONS macro with a value of 1.

Description#

The oneAPI Threading Building Blocks (oneTBB) implementation extends the requirements for user-provided function object from tbb::task_group specification to allow them to return a task_handle object.

Task Bypassing allows developers to reduce task scheduling overhead by providing a hint about which task should be executed next.

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.

tbb::task_handle task_body() {
    tbb::task_handle next_task = group.defer(next_task_body);
    return next_task;
}

API#

Synopsis#

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#

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 F type must meet the Function Objects requirements described in the [function.objects] section of the ISO C++ Standard.

Extension

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.

The returned task_handle must not be explicitly submitted with task_group::run or another submission function, otherwise, the behavior is undefined.

If the returned handle was created by a task_group other than *this, the behavior is undefined.

Example#

The example below demonstrates how to process a sequence in parallel using task_group and the divide-and-conquer pattern.

#define TBB_PREVIEW_TASK_GROUP_EXTENSIONS 1
#include "oneapi/tbb/task_group.h"

template <typename Iterator, typename Function>
struct for_task {
    tbb::task_handle operator()() const {
        tbb::task_handle next_task;

        auto size = end - begin;
        if (size < serial_threshold) {
            // Execute the work serially
            for (Iterator it = begin; it != end; ++it) {
                f(*it);
            }
        } else {
            // Enough work to split the range
            Iterator middle = begin + size / 2;

            // Submit the right subtask for execution
            tg.run(for_task<Iterator, Function>{middle, end, f, tg});

            // Bypass the left subtask
            next_task = tg.defer(for_task<Iterator, Function>{begin, middle, f, tg});
        }
        return next_task;
    }

    Iterator begin;
    Iterator end;
    Function f;
    tbb::task_group& tg;
}; // struct for_task

template <typename RandomAccessIterator, typename Function>
void par_for_each(RandomAccessIterator begin, RandomAccessIterator end, Function f) {
    tbb::task_group tg;
    // Run the root task
    tg.run_and_wait(for_task<RandomAccessIterator, Function>{begin, end, std::move(f), tg});
}