From: Wolfgang Bangerth Date: Tue, 16 Apr 2024 19:53:43 +0000 (-0600) Subject: Import the current development version of TaskFlow 3.7.0. X-Git-Tag: v9.6.0-rc1~310^2 X-Git-Url: https://gitweb.dealii.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F16892%2Fhead;p=dealii.git Import the current development version of TaskFlow 3.7.0. --- diff --git a/bundled/setup_bundled.cmake b/bundled/setup_bundled.cmake index 09df8f1880..776cd76d14 100644 --- a/bundled/setup_bundled.cmake +++ b/bundled/setup_bundled.cmake @@ -100,12 +100,12 @@ option(DEAL_II_FORCE_BUNDLED_TASKFLOW "Always use the bundled taskflow header library instead of an external one." OFF) -set(TASKFLOW_FOLDER "${CMAKE_SOURCE_DIR}/bundled/taskflow-3.6.0") +set(TASKFLOW_FOLDER "${CMAKE_SOURCE_DIR}/bundled/taskflow-3.7.0") macro(feature_taskflow_configure_bundled) - set(TASKFLOW_VERSION "3.6.0") + set(TASKFLOW_VERSION "3.7.0") - list(APPEND DEAL_II_BUNDLED_INCLUDE_DIRS ${TASKFLOW_FOLDER}/include) + list(APPEND DEAL_II_BUNDLED_INCLUDE_DIRS ${TASKFLOW_FOLDER}) endmacro() diff --git a/bundled/taskflow-3.6.0/include/taskflow/algorithm/reduce.hpp b/bundled/taskflow-3.6.0/include/taskflow/algorithm/reduce.hpp deleted file mode 100644 index 64869dc7cb..0000000000 --- a/bundled/taskflow-3.6.0/include/taskflow/algorithm/reduce.hpp +++ /dev/null @@ -1,295 +0,0 @@ -#pragma once - -#include "launch.hpp" - -namespace tf { - -namespace detail { - -// Function: make_reduce_task -template -TF_FORCE_INLINE auto make_reduce_task(B beg, E end, T& init, O bop, P&& part) { - - using B_t = std::decay_t>; - using E_t = std::decay_t>; - using namespace std::string_literals; - - return - [b=beg, e=end, &r=init, bop, part=std::forward

(part)] - (Runtime& rt) mutable { - - // fetch the iterator values - B_t beg = b; - E_t end = e; - - size_t W = rt.executor().num_workers(); - size_t N = std::distance(beg, end); - - // only myself - no need to spawn another graph - if(W <= 1 || N <= part.chunk_size()) { - for(; beg!=end; r = bop(r, *beg++)); - return; - } - - if(N < W) { - W = N; - } - - std::mutex mtx; - - // static partitioner - if constexpr(std::is_same_v, StaticPartitioner>) { - - size_t chunk_size; - - for(size_t w=0, curr_b=0; w lock(mtx); - r = bop(r, *beg); - return; - } - - auto beg1 = beg++; - auto beg2 = beg++; - T sum = bop(*beg1, *beg2); - - // loop reduce - part.loop(N, W, curr_b, chunk_size, - [&, prev_e=curr_b+2](size_t curr_b, size_t curr_e) mutable { - - if(curr_b > prev_e) { - std::advance(beg, curr_b - prev_e); - } - else { - curr_b = prev_e; - } - - for(size_t x=curr_b; x lock(mtx); - r = bop(r, sum); - - }); - } - rt.join(); - } - // dynamic partitioner - else { - std::atomic next(0); - launch_loop(N, W, rt, next, part, [=, &bop, &mtx, &next, &r, &part] () mutable { - // pre-reduce - size_t s0 = next.fetch_add(2, std::memory_order_relaxed); - - if(s0 >= N) { - return; - } - - std::advance(beg, s0); - - if(N - s0 == 1) { - std::lock_guard lock(mtx); - r = bop(r, *beg); - return; - } - - auto beg1 = beg++; - auto beg2 = beg++; - - T sum = bop(*beg1, *beg2); - - // loop reduce - part.loop(N, W, next, - [&, prev_e=s0+2](size_t curr_b, size_t curr_e) mutable { - std::advance(beg, curr_b - prev_e); - for(size_t x=curr_b; x lock(mtx); - r = bop(r, sum); - }); - } - }; -} - -// Function: make_transform_reduce_task -template -TF_FORCE_INLINE auto make_transform_reduce_task( - B beg, E end, T& init, BOP bop, UOP uop, P&& part -) { - - using B_t = std::decay_t>; - using E_t = std::decay_t>; - using namespace std::string_literals; - - return - [b=beg, e=end, &r=init, bop, uop, part=std::forward

(part)] - (Runtime& rt) mutable { - - // fetch the iterator values - B_t beg = b; - E_t end = e; - - size_t W = rt.executor().num_workers(); - size_t N = std::distance(beg, end); - - // only myself - no need to spawn another graph - if(W <= 1 || N <= part.chunk_size()) { - for(; beg!=end; r = bop(std::move(r), uop(*beg++))); - return; - } - - if(N < W) { - W = N; - } - - std::mutex mtx; - - // static partitioner - if constexpr(std::is_same_v, StaticPartitioner>) { - - size_t chunk_size; - - for(size_t w=0, curr_b=0; w lock(mtx); - r = bop(std::move(r), uop(*beg)); - return; - } - - //auto beg1 = beg++; - //auto beg2 = beg++; - //T sum = bop(uop(*beg1), uop(*beg2)); - - T sum = (chunk_size == 1) ? uop(*beg++) : bop(uop(*beg++), uop(*beg++)); - - // loop reduce - part.loop(N, W, curr_b, chunk_size, - [&, prev_e=curr_b+(chunk_size == 1 ? 1 : 2)] - (size_t curr_b, size_t curr_e) mutable { - if(curr_b > prev_e) { - std::advance(beg, curr_b - prev_e); - } - else { - curr_b = prev_e; - } - for(size_t x=curr_b; x lock(mtx); - r = bop(std::move(r), std::move(sum)); - - }); - } - - rt.join(); - } - // dynamic partitioner - else { - std::atomic next(0); - - launch_loop(N, W, rt, next, part, [=, &bop, &uop, &mtx, &next, &r, &part] () mutable { - - // pre-reduce - size_t s0 = next.fetch_add(2, std::memory_order_relaxed); - - if(s0 >= N) { - return; - } - - std::advance(beg, s0); - - if(N - s0 == 1) { - std::lock_guard lock(mtx); - r = bop(std::move(r), uop(*beg)); - return; - } - - auto beg1 = beg++; - auto beg2 = beg++; - - T sum = bop(uop(*beg1), uop(*beg2)); - - // loop reduce - part.loop(N, W, next, - [&, prev_e=s0+2](size_t curr_b, size_t curr_e) mutable { - std::advance(beg, curr_b - prev_e); - for(size_t x=curr_b; x lock(mtx); - r = bop(std::move(r), std::move(sum)); - }); - } - }; -} - -} // end of namespace detail ------------------------------------------------- - -// ---------------------------------------------------------------------------- -// default reduction -// ---------------------------------------------------------------------------- - -// Function: reduce -template -Task FlowBuilder::reduce(B beg, E end, T& init, O bop, P&& part) { - return emplace(detail::make_reduce_task( - beg, end, init, bop, std::forward

(part) - )); -} - -// ---------------------------------------------------------------------------- -// default transform and reduction -// ---------------------------------------------------------------------------- - -// Function: transform_reduce -template -Task FlowBuilder::transform_reduce( - B beg, E end, T& init, BOP bop, UOP uop, P&& part -) { - return emplace(detail::make_transform_reduce_task( - beg, end, init, bop, uop, std::forward

(part) - )); -} - -} // end of namespace tf ----------------------------------------------------- - - - - diff --git a/bundled/taskflow-3.6.0/include/taskflow/core/async.hpp b/bundled/taskflow-3.6.0/include/taskflow/core/async.hpp deleted file mode 100644 index 69788c64b8..0000000000 --- a/bundled/taskflow-3.6.0/include/taskflow/core/async.hpp +++ /dev/null @@ -1,396 +0,0 @@ -#pragma once - -#include "executor.hpp" - -// https://hackmd.io/@sysprog/concurrency-atomics - -namespace tf { - -// ---------------------------------------------------------------------------- -// Async -// ---------------------------------------------------------------------------- - -// Function: async -template -auto Executor::async(const std::string& name, F&& f) { - - _increment_topology(); - - using R = std::invoke_result_t>; - - std::promise p; - auto fu{p.get_future()}; - - auto node = node_pool.animate( - name, 0, nullptr, nullptr, 0, - std::in_place_type_t{}, - _make_promised_async(std::move(p), std::forward(f)) - ); - - _schedule_async_task(node); - - return fu; -} - -// Function: async -template -auto Executor::async(F&& f) { - return async("", std::forward(f)); -} - -// ---------------------------------------------------------------------------- -// Silent Async -// ---------------------------------------------------------------------------- - -// Function: silent_async -template -void Executor::silent_async(const std::string& name, F&& f) { - - _increment_topology(); - - auto node = node_pool.animate( - name, 0, nullptr, nullptr, 0, - std::in_place_type_t{}, std::forward(f) - ); - - _schedule_async_task(node); -} - -// Function: silent_async -template -void Executor::silent_async(F&& f) { - silent_async("", std::forward(f)); -} - -// ---------------------------------------------------------------------------- -// Async Helper Methods -// ---------------------------------------------------------------------------- - -// Function: _make_promised_async -template -auto Executor::_make_promised_async(std::promise&& p, F&& func) { - return [p=make_moc(std::move(p)), func=std::forward(func)]() mutable { - if constexpr(std::is_same_v) { - func(); - p.object.set_value(); - } - else { - p.object.set_value(func()); - } - }; -} - -// Procedure: _schedule_async_task -inline void Executor::_schedule_async_task(Node* node) { - if(auto w = _this_worker(); w) { - _schedule(*w, node); - } - else{ - _schedule(node); - } -} - -// Procedure: _tear_down_async -inline void Executor::_tear_down_async(Node* node) { - // from runtime - if(node->_parent) { - node->_parent->_join_counter.fetch_sub(1, std::memory_order_release); - } - // from executor - else { - _decrement_topology_and_notify(); - } - node_pool.recycle(node); -} - -// ---------------------------------------------------------------------------- -// Silent Dependent Async -// ---------------------------------------------------------------------------- - -// Function: silent_dependent_async -template ...>, void>* -> -tf::AsyncTask Executor::silent_dependent_async(F&& func, Tasks&&... tasks) { - return silent_dependent_async("", std::forward(func), std::forward(tasks)...); -} - -// Function: silent_dependent_async -template ...>, void>* -> -tf::AsyncTask Executor::silent_dependent_async( - const std::string& name, F&& func, Tasks&&... tasks -){ - - _increment_topology(); - - size_t num_dependents = sizeof...(Tasks); - - std::shared_ptr node( - node_pool.animate( - name, 0, nullptr, nullptr, num_dependents, - std::in_place_type_t{}, std::forward(func) - ), - [&](Node* ptr){ node_pool.recycle(ptr); } - ); - - { - std::scoped_lock lock(_asyncs_mutex); - _asyncs.insert(node); - } - - if constexpr(sizeof...(Tasks) > 0) { - (_process_async_dependent(node.get(), tasks, num_dependents), ...); - } - - if(num_dependents == 0) { - _schedule_async_task(node.get()); - } - - return AsyncTask(std::move(node)); -} - -// Function: silent_dependent_async -template , AsyncTask>, void>* -> -tf::AsyncTask Executor::silent_dependent_async(F&& func, I first, I last) { - return silent_dependent_async("", std::forward(func), first, last); -} - -// Function: silent_dependent_async -template , AsyncTask>, void>* -> -tf::AsyncTask Executor::silent_dependent_async( - const std::string& name, F&& func, I first, I last -) { - - _increment_topology(); - - size_t num_dependents = std::distance(first, last); - - std::shared_ptr node( - node_pool.animate( - name, 0, nullptr, nullptr, num_dependents, - std::in_place_type_t{}, std::forward(func) - ), - [&](Node* ptr){ node_pool.recycle(ptr); } - ); - - { - std::scoped_lock lock(_asyncs_mutex); - _asyncs.insert(node); - } - - for(; first != last; first++){ - _process_async_dependent(node.get(), *first, num_dependents); - } - - if(num_dependents == 0) { - _schedule_async_task(node.get()); - } - - return AsyncTask(std::move(node)); -} - -// ---------------------------------------------------------------------------- -// Dependent Async -// ---------------------------------------------------------------------------- - -// Function: dependent_async -template ...>, void>* -> -auto Executor::dependent_async(F&& func, Tasks&&... tasks) { - return dependent_async("", std::forward(func), std::forward(tasks)...); -} - -// Function: dependent_async -template ...>, void>* -> -auto Executor::dependent_async( - const std::string& name, F&& func, Tasks&&... tasks -) { - - _increment_topology(); - - using R = std::invoke_result_t>; - - std::promise p; - auto fu{p.get_future()}; - - size_t num_dependents = sizeof...(tasks); - - std::shared_ptr node( - node_pool.animate( - name, 0, nullptr, nullptr, num_dependents, - std::in_place_type_t{}, - _make_promised_async(std::move(p), std::forward(func)) - ), - [&](Node* ptr){ node_pool.recycle(ptr); } - ); - - { - std::scoped_lock lock(_asyncs_mutex); - _asyncs.insert(node); - } - - if constexpr(sizeof...(Tasks) > 0) { - (_process_async_dependent(node.get(), tasks, num_dependents), ...); - } - - if(num_dependents == 0) { - _schedule_async_task(node.get()); - } - - return std::make_pair(AsyncTask(std::move(node)), std::move(fu)); -} - -// Function: dependent_async -template , AsyncTask>, void>* -> -auto Executor::dependent_async(F&& func, I first, I last) { - return dependent_async("", std::forward(func), first, last); -} - -// Function: dependent_async -template , AsyncTask>, void>* -> -auto Executor::dependent_async( - const std::string& name, F&& func, I first, I last -) { - - _increment_topology(); - - using R = std::invoke_result_t>; - - std::promise p; - auto fu{p.get_future()}; - - size_t num_dependents = std::distance(first, last); - - std::shared_ptr node( - node_pool.animate( - name, 0, nullptr, nullptr, num_dependents, - std::in_place_type_t{}, - _make_promised_async(std::move(p), std::forward(func)) - ), - [&](Node* ptr){ node_pool.recycle(ptr); } - ); - - { - std::scoped_lock lock(_asyncs_mutex); - _asyncs.insert(node); - } - - for(; first != last; first++) { - _process_async_dependent(node.get(), *first, num_dependents); - } - - if(num_dependents == 0) { - _schedule_async_task(node.get()); - } - - return std::make_pair(AsyncTask(std::move(node)), std::move(fu)); -} - -// ---------------------------------------------------------------------------- -// Dependent Async Helper Functions -// ---------------------------------------------------------------------------- - -// Procedure: _process_async_dependent -inline void Executor::_process_async_dependent( - Node* node, tf::AsyncTask& task, size_t& num_dependents -) { - - std::shared_ptr dep; - { - std::scoped_lock lock(_asyncs_mutex); - if(auto itr = _asyncs.find(task._node); itr != _asyncs.end()){ - dep = *itr; - } - } - - // if the dependent task exists - if(dep) { - auto& state = std::get_if(&(dep->_handle))->state; - - add_dependent: - - auto target = Node::AsyncState::UNFINISHED; - - // acquires the lock - if(state.compare_exchange_weak(target, Node::AsyncState::LOCKED, - std::memory_order_acq_rel, - std::memory_order_acquire)) { - dep->_successors.push_back(node); - state.store(Node::AsyncState::UNFINISHED, std::memory_order_release); - } - // dep's state is FINISHED, which means dep finished its callable already - // thus decrement the node's join counter by 1 - else if (target == Node::AsyncState::FINISHED) { - // decrement the counter needs to be the order of acquire and release - // to synchronize with the worker - num_dependents = node->_join_counter.fetch_sub(1, std::memory_order_acq_rel) - 1; - } - // another worker adding an async task that shares the same dependent - else { - goto add_dependent; - } - } - else { - num_dependents = node->_join_counter.fetch_sub(1, std::memory_order_acq_rel) - 1; - } -} - -// Procedure: _tear_down_dependent_async -inline void Executor::_tear_down_dependent_async(Worker& worker, Node* node) { - - // this async task comes from Executor - auto& state = std::get_if(&(node->_handle))->state; - auto target = Node::AsyncState::UNFINISHED; - - while(!state.compare_exchange_weak(target, Node::AsyncState::FINISHED, - std::memory_order_acq_rel, - std::memory_order_relaxed)) { - target = Node::AsyncState::UNFINISHED; - } - - // spaw successors whenever their dependencies are resolved - worker._cache = nullptr; - for(size_t i=0; i_successors.size(); ++i) { - //if(auto s = node->_successors[i]; --(s->_join_counter) == 0) { - if(auto s = node->_successors[i]; - s->_join_counter.fetch_sub(1, std::memory_order_acq_rel) == 1 - ) { - if(worker._cache) { - _schedule(worker, worker._cache); - } - worker._cache = s; - } - } - - // remove myself from the asyncs using extraction to avoid calling - // ~Node inside the lock - typename std::unordered_set>::node_type extracted; - { - std::shared_ptr ptr(node, [](Node*){}); - std::scoped_lock lock(_asyncs_mutex); - extracted = _asyncs.extract(ptr); - // assert(extracted.empty() == false); - } - - _decrement_topology_and_notify(); -} - - - - - -} // end of namespace tf ----------------------------------------------------- - diff --git a/bundled/taskflow-3.6.0/include/taskflow/core/async_task.hpp b/bundled/taskflow-3.6.0/include/taskflow/core/async_task.hpp deleted file mode 100644 index 7c92d8e557..0000000000 --- a/bundled/taskflow-3.6.0/include/taskflow/core/async_task.hpp +++ /dev/null @@ -1,125 +0,0 @@ -#pragma once - -#include "graph.hpp" - -/** -@file async_task.hpp -@brief asynchronous task include file -*/ - -namespace tf { - -// ---------------------------------------------------------------------------- -// AsyncTask -// ---------------------------------------------------------------------------- - -/** -@brief class to create a dependent asynchronous task - -A tf::AsyncTask is a lightweight handle that retains @em shared ownership -of a dependent async task created by an executor. -This shared ownership ensures that the async task remains alive when -adding it to the dependency list of another async task, -thus avoiding the classical [ABA problem](https://en.wikipedia.org/wiki/ABA_problem). - -@code{.cpp} -// main thread retains shared ownership of async task A -tf::AsyncTask A = executor.silent_dependent_async([](){}); - -// task A remains alive (i.e., at least one ref count by the main thread) -// when being added to the dependency list of async task B -tf::AsyncTask B = executor.silent_dependent_async([](){}, A); -@endcode - -Currently, tf::AsyncTask is implemented based on C++ smart pointer std::shared_ptr and -is considered cheap to copy or move as long as only a handful of objects -own it. -When a worker completes an async task, it will remove the task from the executor, -decrementing the number of shared owners by one. -If that counter reaches zero, the task is destroyed. -*/ -class AsyncTask { - - friend class FlowBuilder; - friend class Runtime; - friend class Taskflow; - friend class TaskView; - friend class Executor; - - public: - - /** - @brief constructs an empty task handle - */ - AsyncTask() = default; - - /** - @brief destroys the managed asynchronous task if this is the last owner - */ - ~AsyncTask() = default; - - /** - @brief constructs an task that shares ownership of @c rhs - */ - AsyncTask(const AsyncTask& rhs) = default; - - /** - @brief move-constructs an task from @c rhs - */ - AsyncTask(AsyncTask&& rhs) = default; - - /** - @brief shares ownership of the task managed by @c rhs - */ - AsyncTask& operator = (const AsyncTask& rhs) = default; - - /** - @brief move-assigns the task from @c rhs - */ - AsyncTask& operator = (AsyncTask&& rhs) = default; - - /** - @brief checks if the task stores a non-null shared pointer - */ - bool empty() const; - - /** - @brief release the ownership - */ - void reset(); - - /** - @brief obtains a hash value of the underlying node - */ - size_t hash_value() const; - - private: - - AsyncTask(std::shared_ptr); - - std::shared_ptr _node; -}; - -// Constructor -inline AsyncTask::AsyncTask(std::shared_ptr ptr) : _node {std::move(ptr)} { -} - -// Function: empty -inline bool AsyncTask::empty() const { - return _node == nullptr; -} - -// Function: reset -inline void AsyncTask::reset() { - _node.reset(); -} - -// Function: hash_value -inline size_t AsyncTask::hash_value() const { - return std::hash>{}(_node); -} - -} // end of namespace tf ---------------------------------------------------- - - - diff --git a/bundled/taskflow-3.6.0/include/taskflow/utility/macros.hpp b/bundled/taskflow-3.6.0/include/taskflow/utility/macros.hpp deleted file mode 100644 index e7598cfc99..0000000000 --- a/bundled/taskflow-3.6.0/include/taskflow/utility/macros.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#if defined(_MSC_VER) - #define TF_FORCE_INLINE __forceinline -#elif defined(__GNUC__) && __GNUC__ > 3 - #define TF_FORCE_INLINE __attribute__((__always_inline__)) inline -#else - #define TF_FORCE_INLINE inline -#endif - -#if defined(_MSC_VER) - #define TF_NO_INLINE __declspec(noinline) -#elif defined(__GNUC__) && __GNUC__ > 3 - #define TF_NO_INLINE __attribute__((__noinline__)) -#else - #define TF_NO_INLINE -#endif diff --git a/bundled/taskflow-3.6.0/LICENSE b/bundled/taskflow-3.7.0/LICENSE similarity index 92% rename from bundled/taskflow-3.6.0/LICENSE rename to bundled/taskflow-3.7.0/LICENSE index 37524bc431..aa56ff9223 100644 --- a/bundled/taskflow-3.6.0/LICENSE +++ b/bundled/taskflow-3.7.0/LICENSE @@ -1,6 +1,8 @@ TASKFLOW MIT LICENSE -Copyright (c) 2018-2022 Dr. Tsung-Wei Huang +Copyright (c) 2018-2024 Dr. Tsung-Wei Huang + +The University of Wisconsin at Madison Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/bundled/taskflow-3.6.0/README.md b/bundled/taskflow-3.7.0/README.md similarity index 93% rename from bundled/taskflow-3.6.0/README.md rename to bundled/taskflow-3.7.0/README.md index cbb21a0757..5ea8d9ad2f 100644 --- a/bundled/taskflow-3.6.0/README.md +++ b/bundled/taskflow-3.7.0/README.md @@ -6,7 +6,7 @@ [![Windows](https://github.com/taskflow/taskflow/workflows/Windows/badge.svg)](https://github.com/taskflow/taskflow/actions?query=workflow%3AWindows) [![Wiki](image/api-doc.svg)][documentation] [![TFProf](image/tfprof.svg)](https://taskflow.github.io/tfprof/) -[![Cite](image/cite-tpds.svg)][TPDS21] +[![Cite](image/cite-tpds.svg)][TPDS22] Taskflow helps you quickly write parallel and heterogeneous task programs in modern C++ @@ -22,7 +22,7 @@ Taskflow lets you quickly implement task decomposition strategies that incorporate both regular and irregular compute patterns, together with an efficient *work-stealing* scheduler to optimize your multithreaded performance. -| [Static Tasking](#get-started-with-taskflow) | [Dynamic Tasking](#dynamic-tasking) | +| [Static Tasking](#start-your-first-taskflow-program) | [Subflow Tasking](#create-a-subflow-graph) | | :------------: | :-------------: | | ![](image/static_graph.svg) | | @@ -30,7 +30,7 @@ Taskflow supports conditional tasking for you to make rapid control-flow decisio across dependent tasks to implement cycles and conditions that were otherwise difficult to do with existing tools. -| [Conditional Tasking](#conditional-tasking) | +| [Conditional Tasking](#integrate-control-flow-to-a-task-graph) | | :-----------------: | | ![](image/condition.svg) | @@ -38,7 +38,7 @@ Taskflow is composable. You can create large parallel graphs through composition of modular and reusable blocks that are easier to optimize at an individual scope. -| [Taskflow Composition](#composable-tasking) | +| [Taskflow Composition](#compose-task-graphs) | | :---------------: | |![](image/framework.svg)| @@ -46,7 +46,7 @@ Taskflow supports heterogeneous tasking for you to accelerate a wide range of scientific computing applications by harnessing the power of CPU-GPU collaborative computing. -| [Concurrent CPU-GPU Tasking](#concurrent-cpu-gpu-tasking) | +| [Concurrent CPU-GPU Tasking](#offload-a-task-to-a-gpu) | | :-----------------: | | ![](image/cudaflow.svg) | @@ -77,6 +77,7 @@ The following program (`simple.cpp`) creates four tasks `A`, `B`, `C`, and `D`, where `A` runs before `B` and `C`, and `D` runs after `B` and `C`. When `A` finishes, `B` and `C` can run in parallel. +Try it live on [Compiler Explorer (godbolt)](https://godbolt.org/z/j8hx3xnnx)! @@ -275,26 +276,25 @@ f1_module_task.succeed(f2A, f2B) ## Launch Asynchronous Tasks Taskflow supports *asynchronous* tasking. -You can launch tasks asynchronously to incorporate independent, dynamic -parallelism in your taskflows. +You can launch tasks asynchronously to dynamically explore task graph parallelism. ```cpp tf::Executor executor; -tf::Taskflow taskflow; // create asynchronous tasks directly from an executor -tf::Future> future = executor.async([](){ +std::future future = executor.async([](){ std::cout << "async task returns 1\n"; return 1; }); -executor.silent_async([](){ std::cout << "async task of no return\n"; }); +executor.silent_async([](){ std::cout << "async task does not return\n"; }); -// launch an asynchronous task from a running task -taskflow.emplace([&](){ - executor.async([](){ std::cout << "async task within a task\n"; }); -}); +// create asynchronous tasks with dynamic dependencies +tf::AsyncTask A = executor.silent_dependent_async([](){ printf("A\n"); }); +tf::AsyncTask B = executor.silent_dependent_async([](){ printf("B\n"); }, A); +tf::AsyncTask C = executor.silent_dependent_async([](){ printf("C\n"); }, A); +tf::AsyncTask D = executor.silent_dependent_async([](){ printf("D\n"); }, B, C); -executor.run(taskflow).wait(); +executor.wait_for_all(); ``` ## Execute a Taskflow @@ -446,5 +446,5 @@ You are completely free to re-distribute your work derived from Taskflow. [PayMe]: https://www.paypal.me/twhuang/10 [email me]: mailto:twh760812@gmail.com [Cpp Conference 2018]: https://github.com/CppCon/CppCon2018 -[TPDS21]: https://tsung-wei-huang.github.io/papers/tpds21-taskflow.pdf +[TPDS22]: https://tsung-wei-huang.github.io/papers/tpds21-taskflow.pdf diff --git a/bundled/taskflow-3.6.0/include/taskflow/algorithm/critical.hpp b/bundled/taskflow-3.7.0/taskflow/algorithm/critical.hpp similarity index 97% rename from bundled/taskflow-3.6.0/include/taskflow/algorithm/critical.hpp rename to bundled/taskflow-3.7.0/taskflow/algorithm/critical.hpp index c781d28271..46ac395951 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/algorithm/critical.hpp +++ b/bundled/taskflow-3.7.0/taskflow/algorithm/critical.hpp @@ -18,7 +18,7 @@ namespace tf { @brief class to create a critical region of limited workers to run tasks -tf::CriticalSection is a warpper over tf::Semaphore and is specialized for +tf::CriticalSection is a wrapper over tf::Semaphore and is specialized for limiting the maximum concurrency over a set of tasks. A critical section starts with an initial count representing that limit. When a task is added to the critical section, diff --git a/bundled/taskflow-3.6.0/include/taskflow/algorithm/data_pipeline.hpp b/bundled/taskflow-3.7.0/taskflow/algorithm/data_pipeline.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/algorithm/data_pipeline.hpp rename to bundled/taskflow-3.7.0/taskflow/algorithm/data_pipeline.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/algorithm/find.hpp b/bundled/taskflow-3.7.0/taskflow/algorithm/find.hpp similarity index 75% rename from bundled/taskflow-3.6.0/include/taskflow/algorithm/find.hpp rename to bundled/taskflow-3.7.0/taskflow/algorithm/find.hpp index ab0d8011de..cb3d080c26 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/algorithm/find.hpp +++ b/bundled/taskflow-3.7.0/taskflow/algorithm/find.hpp @@ -8,13 +8,13 @@ namespace detail { // Function: find_if_loop template -TF_FORCE_INLINE bool find_if_loop( +bool find_if_loop( std::atomic& offset, Iterator& beg, size_t& prev_e, size_t curr_b, size_t curr_e, - Predicate&& predicate + Predicate predicate ) { // early prune if(offset.load(std::memory_order_relaxed) < curr_b) { @@ -33,13 +33,13 @@ TF_FORCE_INLINE bool find_if_loop( // Function: find_if_not_loop template -TF_FORCE_INLINE bool find_if_not_loop( +bool find_if_not_loop( std::atomic& offset, Iterator& beg, size_t& prev_e, size_t curr_b, size_t curr_e, - Predicate&& predicate + Predicate predicate ) { // early prune @@ -57,30 +57,29 @@ TF_FORCE_INLINE bool find_if_not_loop( return false; } +} // namespace detail -------------------------------------------------------- + // Function: make_find_if_task -template -TF_FORCE_INLINE auto make_find_if_task( - B first, E last, T& result, UOP predicate, P&& part -) { +template +auto make_find_if_task(B first, E last, T& result, UOP predicate, P part = P()) { using B_t = std::decay_t>; using E_t = std::decay_t>; - using namespace std::string_literals; - return - [b=first, e=last, predicate, &result, part=std::forward

(part)] - (Runtime& rt) mutable { + return [=, &result] (Runtime& rt) mutable { // fetch the stateful values - B_t beg = b; - E_t end = e; + B_t beg = first; + E_t end = last; size_t W = rt.executor().num_workers(); size_t N = std::distance(beg, end); // only myself - no need to spawn another graph if(W <= 1 || N <= part.chunk_size()) { - result = std::find_if(beg, end, predicate); + launch_loop(part, [&](){ + result = std::find_if(beg, end, predicate); + }); return; } @@ -91,7 +90,7 @@ TF_FORCE_INLINE auto make_find_if_task( std::atomic offset(N); // static partitioner - if constexpr(std::is_same_v, StaticPartitioner>) { + if constexpr(part.type() == PartitionerType::STATIC) { size_t chunk_size; @@ -99,21 +98,20 @@ TF_FORCE_INLINE auto make_find_if_task( chunk_size = part.adjusted_chunk_size(N, W, w); - launch_loop(W, w, rt, - [N, W, curr_b, chunk_size, beg, &predicate, &offset, &part] - () mutable { + launch_loop(W, w, rt, part, + [N, W, curr_b, chunk_size, beg, &predicate, &offset, &part] () mutable { part.loop_until(N, W, curr_b, chunk_size, - [&, prev_e=size_t{0}](size_t curr_b, size_t curr_e) mutable { + [&, prev_e=size_t{0}](size_t part_b, size_t part_e) mutable { return detail::find_if_loop( - offset, beg, prev_e, curr_b, curr_e, predicate + offset, beg, prev_e, part_b, part_e, predicate ); } - ); + ); } ); } - rt.join(); + rt.corun_all(); } // dynamic partitioner else { @@ -126,7 +124,7 @@ TF_FORCE_INLINE auto make_find_if_task( offset, beg, prev_e, curr_b, curr_e, predicate ); } - ); + ); } ); } @@ -137,29 +135,26 @@ TF_FORCE_INLINE auto make_find_if_task( } // Function: make_find_if_not_task -template -TF_FORCE_INLINE auto make_find_if_not_task( - B first, E last, T& result, UOP predicate, P&& part -) { +template +auto make_find_if_not_task(B first, E last, T& result, UOP predicate, P part = P()) { using B_t = std::decay_t>; using E_t = std::decay_t>; - using namespace std::string_literals; - return - [b=first, e=last, predicate, &result, part=std::forward

(part)] - (Runtime& rt) mutable { + return [=, &result] (Runtime& rt) mutable { // fetch the stateful values - B_t beg = b; - E_t end = e; + B_t beg = first; + E_t end = last; size_t W = rt.executor().num_workers(); size_t N = std::distance(beg, end); // only myself - no need to spawn another graph if(W <= 1 || N <= part.chunk_size()) { - result = std::find_if_not(beg, end, predicate); + launch_loop(part, [&](){ + result = std::find_if_not(beg, end, predicate); + }); return; } @@ -170,7 +165,7 @@ TF_FORCE_INLINE auto make_find_if_not_task( std::atomic offset(N); // static partitioner - if constexpr(std::is_same_v, StaticPartitioner>) { + if constexpr(part.type() == PartitionerType::STATIC) { size_t chunk_size; @@ -178,20 +173,20 @@ TF_FORCE_INLINE auto make_find_if_not_task( chunk_size = part.adjusted_chunk_size(N, W, w); - launch_loop(W, w, rt, + launch_loop(W, w, rt, part, [N, W, curr_b, chunk_size, beg, &predicate, &offset, &part] () mutable { part.loop_until(N, W, curr_b, chunk_size, - [&, prev_e=size_t{0}](size_t curr_b, size_t curr_e) mutable { + [&, prev_e=size_t{0}](size_t part_b, size_t part_e) mutable { return detail::find_if_not_loop( - offset, beg, prev_e, curr_b, curr_e, predicate + offset, beg, prev_e, part_b, part_e, predicate ); } - ); + ); } ); } - rt.join(); + rt.corun_all(); } // dynamic partitioner else { @@ -204,7 +199,7 @@ TF_FORCE_INLINE auto make_find_if_not_task( offset, beg, prev_e, curr_b, curr_e, predicate ); } - ); + ); } ); } @@ -215,29 +210,26 @@ TF_FORCE_INLINE auto make_find_if_not_task( } // Function: make_min_element_task -template -TF_FORCE_INLINE auto make_min_element_task( - B first, E last, T& result, C comp, P&& part -) { +template +auto make_min_element_task(B first, E last, T& result, C comp, P part = P()) { using B_t = std::decay_t>; using E_t = std::decay_t>; - using namespace std::string_literals; - return - [b=first, e=last, &result, comp, part=std::forward

(part)] - (Runtime& rt) mutable { + return [=, &result] (Runtime& rt) mutable { // fetch the iterator values - B_t beg = b; - E_t end = e; + B_t beg = first; + E_t end = last; size_t W = rt.executor().num_workers(); size_t N = std::distance(beg, end); // only myself - no need to spawn another graph if(W <= 1 || N <= part.chunk_size()) { - result = std::min_element(beg, end, comp); + launch_loop(part, [&](){ + result = std::min_element(beg, end, comp); + }); return; } @@ -252,7 +244,7 @@ TF_FORCE_INLINE auto make_min_element_task( N--; // static partitioner - if constexpr(std::is_same_v, StaticPartitioner>) { + if constexpr(part.type() == PartitionerType::STATIC) { size_t chunk_size; @@ -262,9 +254,8 @@ TF_FORCE_INLINE auto make_min_element_task( // variable sum needs to avoid copy at the first step chunk_size = std::max(size_t{2}, part.adjusted_chunk_size(N, W, w)); - launch_loop(W, w, rt, + launch_loop(W, w, rt, part, [beg, curr_b, N, W, chunk_size, &comp, &mutex, &result, &part] () mutable { - std::advance(beg, curr_b); if(N - curr_b == 1) { @@ -281,21 +272,21 @@ TF_FORCE_INLINE auto make_min_element_task( // loop reduce part.loop(N, W, curr_b, chunk_size, - [&, prev_e=curr_b+2](size_t curr_b, size_t curr_e) mutable { + [&, prev_e=curr_b+2](size_t part_b, size_t part_e) mutable { - if(curr_b > prev_e) { - std::advance(beg, curr_b - prev_e); + if(part_b > prev_e) { + std::advance(beg, part_b - prev_e); } else { - curr_b = prev_e; + part_b = prev_e; } - for(size_t x=curr_b; x -TF_FORCE_INLINE auto make_max_element_task(B first, E last, T& result, C comp, P&& part) { +template +auto make_max_element_task(B first, E last, T& result, C comp, P part = P()) { using B_t = std::decay_t>; using E_t = std::decay_t>; - using namespace std::string_literals; - return - [b=first, e=last, &result, comp, part=std::forward

(part)] - (Runtime& rt) mutable { + return [=, &result] (Runtime& rt) mutable { // fetch the iterator values - B_t beg = b; - E_t end = e; + B_t beg = first; + E_t end = last; size_t W = rt.executor().num_workers(); size_t N = std::distance(beg, end); // only myself - no need to spawn another graph if(W <= 1 || N <= part.chunk_size()) { - result = std::max_element(beg, end, comp); + launch_loop(part, [&](){ + result = std::max_element(beg, end, comp); + }); return; } @@ -395,7 +385,7 @@ TF_FORCE_INLINE auto make_max_element_task(B first, E last, T& result, C comp, P N--; // static partitioner - if constexpr(std::is_same_v, StaticPartitioner>) { + if constexpr(part.type() == PartitionerType::STATIC) { size_t chunk_size; @@ -405,9 +395,8 @@ TF_FORCE_INLINE auto make_max_element_task(B first, E last, T& result, C comp, P // variable sum needs to avoid copy at the first step chunk_size = std::max(size_t{2}, part.adjusted_chunk_size(N, W, w)); - launch_loop(W, w, rt, + launch_loop(W, w, rt, part, [beg, curr_b, N, W, chunk_size, &comp, &mutex, &result, &part] () mutable { - std::advance(beg, curr_b); if(N - curr_b == 1) { @@ -424,21 +413,21 @@ TF_FORCE_INLINE auto make_max_element_task(B first, E last, T& result, C comp, P // loop reduce part.loop(N, W, curr_b, chunk_size, - [&, prev_e=curr_b+2](size_t curr_b, size_t curr_e) mutable { + [&, prev_e=curr_b+2](size_t part_b, size_t part_e) mutable { - if(curr_b > prev_e) { - std::advance(beg, curr_b - prev_e); + if(part_b > prev_e) { + std::advance(beg, part_b - prev_e); } else { - curr_b = prev_e; + part_b = prev_e; } - for(size_t x=curr_b; x -Task tf::FlowBuilder::find_if(B first, E last, T& result, UOP predicate, P&& part) { - return emplace(detail::make_find_if_task( - first, last, result, predicate, std::forward

(part) - )); +Task tf::FlowBuilder::find_if(B first, E last, T& result, UOP predicate, P part) { + return emplace(make_find_if_task(first, last, result, predicate, part)); } // Function: find_if_not template -Task tf::FlowBuilder::find_if_not(B first, E last, T& result, UOP predicate, P&& part) { - return emplace(detail::make_find_if_not_task( - first, last, result, predicate, std::forward

(part) - )); +Task tf::FlowBuilder::find_if_not(B first, E last, T& result, UOP predicate, P part) { + return emplace(make_find_if_not_task(first, last, result, predicate, part)); } // ---------------------------------------------------------------------------- @@ -526,10 +511,8 @@ Task tf::FlowBuilder::find_if_not(B first, E last, T& result, UOP predicate, P&& // Function: min_element template -Task FlowBuilder::min_element(B first, E last, T& result, C comp, P&& part) { - return emplace(detail::make_min_element_task( - first, last, result, comp, std::forward

(part) - )); +Task FlowBuilder::min_element(B first, E last, T& result, C comp, P part) { + return emplace(make_min_element_task(first, last, result, comp, part)); } // ---------------------------------------------------------------------------- @@ -538,10 +521,8 @@ Task FlowBuilder::min_element(B first, E last, T& result, C comp, P&& part) { // Function: max_element template -Task FlowBuilder::max_element(B first, E last, T& result, C comp, P&& part) { - return emplace(detail::make_max_element_task( - first, last, result, comp, std::forward

(part) - )); +Task FlowBuilder::max_element(B first, E last, T& result, C comp, P part) { + return emplace(make_max_element_task(first, last, result, comp, part)); } } // end of namespace tf ----------------------------------------------------- diff --git a/bundled/taskflow-3.6.0/include/taskflow/algorithm/for_each.hpp b/bundled/taskflow-3.7.0/taskflow/algorithm/for_each.hpp similarity index 59% rename from bundled/taskflow-3.6.0/include/taskflow/algorithm/for_each.hpp rename to bundled/taskflow-3.7.0/taskflow/algorithm/for_each.hpp index d15958abd7..8c98e84ea2 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/algorithm/for_each.hpp +++ b/bundled/taskflow-3.7.0/taskflow/algorithm/for_each.hpp @@ -4,17 +4,14 @@ namespace tf { -namespace detail { - // Function: make_for_each_task -template -TF_FORCE_INLINE auto make_for_each_task(B beg, E end, C c, P&& part) { - +template +auto make_for_each_task(B b, E e, C c, P part = P()) { + using B_t = std::decay_t>; using E_t = std::decay_t>; - using namespace std::string_literals; - return [b=beg, e=end, c, part=std::forward

(part)] (Runtime& rt) mutable { + return [=] (Runtime& rt) mutable { // fetch the stateful values B_t beg = b; @@ -25,7 +22,9 @@ TF_FORCE_INLINE auto make_for_each_task(B beg, E end, C c, P&& part) { // only myself - no need to spawn another graph if(W <= 1 || N <= part.chunk_size()) { - std::for_each(beg, end, c); + launch_loop(part, [&](){ + std::for_each(beg, end, c); + }); return; } @@ -34,69 +33,73 @@ TF_FORCE_INLINE auto make_for_each_task(B beg, E end, C c, P&& part) { } // static partitioner - if constexpr(std::is_same_v, StaticPartitioner>) { + if constexpr(part.type() == PartitionerType::STATIC) { size_t chunk_size; for(size_t w=0, curr_b=0; w next(0); launch_loop(N, W, rt, next, part, [=, &c, &next, &part] () mutable { part.loop(N, W, next, - [&, prev_e=size_t{0}](size_t curr_b, size_t curr_e) mutable { - std::advance(beg, curr_b - prev_e); - for(size_t x = curr_b; x -TF_FORCE_INLINE auto make_for_each_index_task(B beg, E end, S inc, C c, P&& part){ - - using namespace std::string_literals; +template +auto make_for_each_index_task(B b, E e, S s, C c, P part = P()){ using B_t = std::decay_t>; using E_t = std::decay_t>; using S_t = std::decay_t>; - return [b=beg, e=end, a=inc, c, part=std::forward

(part)] - (Runtime& rt) mutable { + return [=] (Runtime& rt) mutable { // fetch the iterator values B_t beg = b; E_t end = e; - S_t inc = a; + S_t inc = s; + + // nothing to be done if the range is invalid + if(is_range_invalid(beg, end, inc)) { + return; + } size_t W = rt.executor().num_workers(); size_t N = distance(beg, end, inc); // only myself - no need to spawn another graph if(W <= 1 || N <= part.chunk_size()) { - for(size_t x=0; x, StaticPartitioner>) { - + if constexpr(part.type() == PartitionerType::STATIC) { size_t chunk_size; - for(size_t w=0, curr_b=0; w(curr_b) * inc + beg; - for(size_t x=curr_b; x(part_b) * inc + beg; + for(size_t x=part_b; x next(0); launch_loop(N, W, rt, next, part, [=, &c, &next, &part] () mutable { part.loop(N, W, next, - [&](size_t curr_b, size_t curr_e) { - auto idx = static_cast(curr_b) * inc + beg; - for(size_t x=curr_b; x(part_b) * inc + beg; + for(size_t x=part_b; x -Task FlowBuilder::for_each(B beg, E end, C c, P&& part) { +Task FlowBuilder::for_each(B beg, E end, C c, P part) { return emplace( - detail::make_for_each_task(beg, end, c, std::forward

(part)) + make_for_each_task(beg, end, c, part) ); } @@ -162,9 +161,9 @@ Task FlowBuilder::for_each(B beg, E end, C c, P&& part) { // Function: for_each_index template -Task FlowBuilder::for_each_index(B beg, E end, S inc, C c, P&& part){ +Task FlowBuilder::for_each_index(B beg, E end, S inc, C c, P part){ return emplace( - detail::make_for_each_index_task(beg, end, inc, c, std::forward

(part)) + make_for_each_index_task(beg, end, inc, c, part) ); } diff --git a/bundled/taskflow-3.6.0/include/taskflow/algorithm/launch.hpp b/bundled/taskflow-3.7.0/taskflow/algorithm/launch.hpp similarity index 57% rename from bundled/taskflow-3.6.0/include/taskflow/algorithm/launch.hpp rename to bundled/taskflow-3.7.0/taskflow/algorithm/launch.hpp index 363223e60e..527fb2fe07 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/algorithm/launch.hpp +++ b/bundled/taskflow-3.7.0/taskflow/algorithm/launch.hpp @@ -1,9 +1,26 @@ #pragma once +#include #include "../core/async.hpp" namespace tf { +// Function: launch_loop +template +TF_FORCE_INLINE void launch_loop(P part, Loop loop) { + + constexpr bool is_default_wrapper_v = std::is_same_v< + typename std::decay_t

::closure_wrapper_type, DefaultClosureWrapper + >; + + if constexpr(is_default_wrapper_v) { + loop(); + } + else { + std::invoke(part.closure_wrapper(), loop); + } +} + // Function: launch_loop template TF_FORCE_INLINE void launch_loop( @@ -11,8 +28,8 @@ TF_FORCE_INLINE void launch_loop( size_t W, Runtime& rt, std::atomic& next, - P&& part, - Loop&& loop + P part, + Loop loop ) { //static_assert(std::is_lvalue_reference_v, ""); @@ -27,31 +44,32 @@ TF_FORCE_INLINE void launch_loop( } // tail optimization if(r <= part.chunk_size() || w == W-1) { - loop(); + launch_loop(part, loop); break; } else { - rt.silent_async_unchecked("loop-"s + std::to_string(w), loop); + rt.silent_async_unchecked([=](){ launch_loop(part, loop); }); } } - rt.join(); + rt.corun_all(); } // Function: launch_loop -template +template TF_FORCE_INLINE void launch_loop( size_t W, size_t w, Runtime& rt, - Loop&& loop + P part, + Loop loop ) { using namespace std::string_literals; if(w == W-1) { - loop(); + launch_loop(part, loop); } else { - rt.silent_async_unchecked("loop-"s + std::to_string(w), loop); + rt.silent_async_unchecked([=](){ launch_loop(part, loop); }); } } diff --git a/bundled/taskflow-3.6.0/include/taskflow/algorithm/partitioner.hpp b/bundled/taskflow-3.7.0/taskflow/algorithm/partitioner.hpp similarity index 55% rename from bundled/taskflow-3.6.0/include/taskflow/algorithm/partitioner.hpp rename to bundled/taskflow-3.7.0/taskflow/algorithm/partitioner.hpp index 4a253fafe6..04406f8343 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/algorithm/partitioner.hpp +++ b/bundled/taskflow-3.7.0/taskflow/algorithm/partitioner.hpp @@ -11,6 +11,52 @@ namespace tf { +/** +@enum PartitionerType + +@brief enumeration of all partitioner types +*/ +enum class PartitionerType : int { + /** @brief static partitioner type */ + STATIC, + /** @brief dynamic partitioner type */ + DYNAMIC +}; + + +//template +//class PartitionInvoker : public PartitionerBase { +// +// protected +// +// C _closure; +// +// template +// auto operator()(ArgsT&&... args) { +// return std::invoke(closure, std::forward(args)...); +// } +// +// template +// auto operator()(ArgsT&&... args) const { +// return std::invoke(closure, std::forward(args)...); +// } +// +//}; + +/** +@struct DefaultClosureWrapper + +@brief default closure wrapper that simplies runs the given closure as is +*/ +struct DefaultClosureWrapper { +}; + +/** +@private +*/ +struct IsPartitioner { +}; + // ---------------------------------------------------------------------------- // Partitioner Base // ---------------------------------------------------------------------------- @@ -20,17 +66,19 @@ namespace tf { @brief class to derive a partitioner for scheduling parallel algorithms +@tparam C closure wrapper type + The class provides base methods to derive a partitioner that can be used to schedule parallel iterations (e.g., tf::Taskflow::for_each). An partitioner defines the scheduling method for running parallel algorithms, such tf::Taskflow::for_each, tf::Taskflow::reduce, and so on. -By default, we provide the following partitioners: +By default, we provide the following partitioners: -+ tf::GuidedPartitioner to enable guided scheduling algorithm of adaptive chunk size ++ tf::GuidedPartitioner to enable guided scheduling algorithm of adaptive chunk size + tf::DynamicPartitioner to enable dynamic scheduling algorithm of equal chunk size -+ tf::StaticPartitioner to enable static scheduling algorithm of static chunk size -+ tf::RandomPartitioner to enable random scheduling algorithm of random chunk size ++ tf::StaticPartitioner to enable static scheduling algorithm of static chunk size ++ tf::RandomPartitioner to enable random scheduling algorithm of random chunk size Depending on applications, partitioning algorithms can impact the performance a lot. @@ -40,10 +88,53 @@ On the other hand, if the work unit per iteration is irregular and unbalanced, tf::GuidedPartitioner or tf::DynamicPartitioner can outperform tf::StaticPartitioner. In most situations, tf::GuidedPartitioner can deliver decent performance and is thus used as our default partitioner. + +@note +Giving the partition size of 0 lets the %Taskflow runtime automatically determines +the partition size for the given partitioner. + + +In addition to partition size, the application can specify a closure wrapper +for a partitioner. +A closure wrapper allows the application to wrapper a partitioned task +(i.e., closure) with a custom function object that performs additional tasks. +For example: + +@code{.cpp} +std::atomic count = 0; +tf::Taskflow taskflow; +taskflow.for_each_index(0, 100, 1, + [](){ + printf("%d\n", i); + }, + tf::StaticPartitioner(0, [](auto&& closure){ + // do something before invoking the partitioned task + // ... + + // invoke the partitioned task + closure(); + + // do something else after invoking the partitioned task + // ... + } +); +executor.run(taskflow).wait(); +@endcode + +@note +The default closure wrapper (tf::DefaultClosureWrapper) does nothing but invoke +the partitioned task (closure). + */ -class PartitionerBase { +template +class PartitionerBase : public IsPartitioner { public: + + /** + @brief the closure type + */ + using closure_wrapper_type = C; /** @brief default constructor @@ -54,6 +145,14 @@ class PartitionerBase { @brief construct a partitioner with the given chunk size */ explicit PartitionerBase(size_t chunk_size) : _chunk_size {chunk_size} {} + + /** + @brief construct a partitioner with the given chunk size and closure wrapper + */ + PartitionerBase(size_t chunk_size, C&& closure_wrapper) : + _chunk_size {chunk_size}, + _closure_wrapper {std::forward(closure_wrapper)} { + } /** @brief query the chunk size of this partitioner @@ -65,41 +164,100 @@ class PartitionerBase { */ void chunk_size(size_t cz) { _chunk_size = cz; } + /** + @brief acquire an immutable access to the closure wrapper object + */ + const C& closure_wrapper() const { return _closure_wrapper; } + + /** + @brief modify the closure wrapper object + */ + template + void closure_wrapper(F&& fn) { _closure_wrapper = std::forward(fn); } + protected: /** @brief chunk size */ size_t _chunk_size{0}; + + /** + @brief closure wrapper + */ + C _closure_wrapper; }; // ---------------------------------------------------------------------------- // Guided Partitioner // ---------------------------------------------------------------------------- - + /** @class GuidedPartitioner +@tparam C closure wrapper type (default tf::DefaultClosureWrapper) + @brief class to construct a guided partitioner for scheduling parallel algorithms The size of a partition is proportional to the number of unassigned iterations divided by the number of workers, and the size will gradually decrease to the given chunk size. The last partition may be smaller than the chunk size. + +In addition to partition size, the application can specify a closure wrapper +for a guided partitioner. +A closure wrapper allows the application to wrapper a partitioned task +(i.e., closure) with a custom function object that performs additional tasks. +For example: + +@code{.cpp} +std::atomic count = 0; +tf::Taskflow taskflow; +taskflow.for_each_index(0, 100, 1, + [](){ + printf("%d\n", i); + }, + tf::GuidedPartitioner(0, [](auto&& closure){ + // do something before invoking the partitioned task + // ... + + // invoke the partitioned task + closure(); + + // do something else after invoking the partitioned task + // ... + } +); +executor.run(taskflow).wait(); +@endcode */ -class GuidedPartitioner : public PartitionerBase { +template +class GuidedPartitioner : public PartitionerBase { public: + /** + @brief queries the partition type (dynamic) + */ + static constexpr PartitionerType type() { return PartitionerType::DYNAMIC; } + /** @brief default constructor */ - GuidedPartitioner() : PartitionerBase{1} {} + GuidedPartitioner() = default; /** @brief construct a guided partitioner with the given chunk size + */ - explicit GuidedPartitioner(size_t sz) : PartitionerBase (sz) {} + explicit GuidedPartitioner(size_t sz) : PartitionerBase (sz) {} + + /** + @brief construct a guided partitioner with the given chunk size and the closure + */ + explicit GuidedPartitioner(size_t sz, C&& closure) : + PartitionerBase(sz, std::forward(closure)) { + } // -------------------------------------------------------------------------- // scheduling methods @@ -112,13 +270,10 @@ class GuidedPartitioner : public PartitionerBase { std::enable_if_t, void>* = nullptr > void loop( - size_t N, - size_t W, - std::atomic& next, - F&& func + size_t N, size_t W, std::atomic& next, F&& func ) const { - size_t chunk_size = (_chunk_size == 0) ? size_t{1} : _chunk_size; + size_t chunk_size = (this->_chunk_size == 0) ? size_t{1} : this->_chunk_size; size_t p1 = 2 * W * (chunk_size + 1); float p2 = 0.5f / static_cast(W); @@ -163,13 +318,10 @@ class GuidedPartitioner : public PartitionerBase { std::enable_if_t, void>* = nullptr > void loop_until( - size_t N, - size_t W, - std::atomic& next, - F&& func + size_t N, size_t W, std::atomic& next, F&& func ) const { - size_t chunk_size = (_chunk_size == 0) ? size_t{1} : _chunk_size; + size_t chunk_size = (this->_chunk_size == 0) ? size_t{1} : this->_chunk_size; size_t p1 = 2 * W * (chunk_size + 1); float p2 = 0.5f / static_cast(W); @@ -210,6 +362,7 @@ class GuidedPartitioner : public PartitionerBase { } } } + }; // ---------------------------------------------------------------------------- @@ -221,29 +374,71 @@ class GuidedPartitioner : public PartitionerBase { @brief class to construct a dynamic partitioner for scheduling parallel algorithms +@tparam C closure wrapper type (default tf::DefaultClosureWrapper) + The partitioner splits iterations into many partitions each of size equal to the given chunk size. Different partitions are distributed dynamically to workers without any specific order. + +In addition to partition size, the application can specify a closure wrapper +for a dynamic partitioner. +A closure wrapper allows the application to wrapper a partitioned task +(i.e., closure) with a custom function object that performs additional tasks. +For example: + +@code{.cpp} +std::atomic count = 0; +tf::Taskflow taskflow; +taskflow.for_each_index(0, 100, 1, + [](){ + printf("%d\n", i); + }, + tf::DynamicPartitioner(0, [](auto&& closure){ + // do something before invoking the partitioned task + // ... + + // invoke the partitioned task + closure(); + + // do something else after invoking the partitioned task + // ... + } +); +executor.run(taskflow).wait(); +@endcode */ -class DynamicPartitioner : public PartitionerBase { +template +class DynamicPartitioner : public PartitionerBase { public: + + /** + @brief queries the partition type (dynamic) + */ + static constexpr PartitionerType type() { return PartitionerType::DYNAMIC; } /** @brief default constructor */ - DynamicPartitioner() : PartitionerBase{1} {}; + DynamicPartitioner() = default; /** @brief construct a dynamic partitioner with the given chunk size */ - explicit DynamicPartitioner(size_t sz) : PartitionerBase (sz) {} + explicit DynamicPartitioner(size_t sz) : PartitionerBase(sz) {} + + /** + @brief construct a dynamic partitioner with the given chunk size and the closure + */ + explicit DynamicPartitioner(size_t sz, C&& closure) : + PartitionerBase(sz, std::forward(closure)) { + } // -------------------------------------------------------------------------- // scheduling methods // -------------------------------------------------------------------------- - + /** @private */ @@ -251,13 +446,10 @@ class DynamicPartitioner : public PartitionerBase { std::enable_if_t, void>* = nullptr > void loop( - size_t N, - size_t, - std::atomic& next, - F&& func + size_t N, size_t, std::atomic& next, F&& func ) const { - size_t chunk_size = (_chunk_size == 0) ? size_t{1} : _chunk_size; + size_t chunk_size = (this->_chunk_size == 0) ? size_t{1} : this->_chunk_size; size_t curr_b = next.fetch_add(chunk_size, std::memory_order_relaxed); while(curr_b < N) { @@ -273,13 +465,10 @@ class DynamicPartitioner : public PartitionerBase { std::enable_if_t, void>* = nullptr > void loop_until( - size_t N, - size_t, - std::atomic& next, - F&& func + size_t N, size_t, std::atomic& next, F&& func ) const { - size_t chunk_size = (_chunk_size == 0) ? size_t{1} : _chunk_size; + size_t chunk_size = (this->_chunk_size == 0) ? size_t{1} : this->_chunk_size; size_t curr_b = next.fetch_add(chunk_size, std::memory_order_relaxed); while(curr_b < N) { @@ -289,6 +478,7 @@ class DynamicPartitioner : public PartitionerBase { curr_b = next.fetch_add(chunk_size, std::memory_order_relaxed); } } + }; // ---------------------------------------------------------------------------- @@ -298,7 +488,9 @@ class DynamicPartitioner : public PartitionerBase { /** @class StaticPartitioner -@brief class to construct a dynamic partitioner for scheduling parallel algorithms +@brief class to construct a static partitioner for scheduling parallel algorithms + +@tparam C closure wrapper type (default tf::DefaultClosureWrapper) The partitioner divides iterations into chunks and distributes chunks to workers in order. @@ -312,20 +504,60 @@ taskflow.for_each( ); executor.run(taskflow).run(); @endcode + +In addition to partition size, the application can specify a closure wrapper +for a static partitioner. +A closure wrapper allows the application to wrapper a partitioned task +(i.e., closure) with a custom function object that performs additional tasks. +For example: + +@code{.cpp} +std::atomic count = 0; +tf::Taskflow taskflow; +taskflow.for_each_index(0, 100, 1, + [](){ + printf("%d\n", i); + }, + tf::StaticPartitioner(0, [](auto&& closure){ + // do something before invoking the partitioned task + // ... + + // invoke the partitioned task + closure(); + + // do something else after invoking the partitioned task + // ... + } +); +executor.run(taskflow).wait(); +@endcode */ -class StaticPartitioner : public PartitionerBase { +template +class StaticPartitioner : public PartitionerBase { public: + + /** + @brief queries the partition type (static) + */ + static constexpr PartitionerType type() { return PartitionerType::STATIC; } /** @brief default constructor */ - StaticPartitioner() : PartitionerBase{0} {}; + StaticPartitioner() = default; /** - @brief construct a dynamic partitioner with the given chunk size + @brief construct a static partitioner with the given chunk size */ - explicit StaticPartitioner(size_t sz) : PartitionerBase(sz) {} + explicit StaticPartitioner(size_t sz) : PartitionerBase(sz) {} + + /** + @brief construct a static partitioner with the given chunk size and the closure + */ + explicit StaticPartitioner(size_t sz, C&& closure) : + PartitionerBase(sz, std::forward(closure)) { + } /** @brief queries the adjusted chunk size @@ -335,13 +567,13 @@ class StaticPartitioner : public PartitionerBase { @c W is the number of workers, and @c w is the worker ID. */ size_t adjusted_chunk_size(size_t N, size_t W, size_t w) const { - return _chunk_size ? _chunk_size : N/W + (w < N%W); + return this->_chunk_size ? this->_chunk_size : N/W + (w < N%W); } // -------------------------------------------------------------------------- // scheduling methods // -------------------------------------------------------------------------- - + /** @private */ @@ -349,11 +581,7 @@ class StaticPartitioner : public PartitionerBase { std::enable_if_t, void>* = nullptr > void loop( - size_t N, - size_t W, - size_t curr_b, - size_t chunk_size, - F&& func + size_t N, size_t W, size_t curr_b, size_t chunk_size, F&& func ) { size_t stride = W * chunk_size; while(curr_b < N) { @@ -370,11 +598,7 @@ class StaticPartitioner : public PartitionerBase { std::enable_if_t, void>* = nullptr > void loop_until( - size_t N, - size_t W, - size_t curr_b, - size_t chunk_size, - F&& func + size_t N, size_t W, size_t curr_b, size_t chunk_size, F&& func ) { size_t stride = W * chunk_size; while(curr_b < N) { @@ -396,15 +620,49 @@ class StaticPartitioner : public PartitionerBase { @brief class to construct a random partitioner for scheduling parallel algorithms +@tparam C closure wrapper type (default tf::DefaultClosureWrapper) + Similar to tf::DynamicPartitioner, the partitioner splits iterations into many partitions but each with a random chunk size in the range, c = [alpha * N * W, beta * N * W]. By default, @c alpha is 0.01 and @c beta is 0.5, respectively. +In addition to partition size, the application can specify a closure wrapper +for a random partitioner. +A closure wrapper allows the application to wrapper a partitioned task +(i.e., closure) with a custom function object that performs additional tasks. +For example: + +@code{.cpp} +std::atomic count = 0; +tf::Taskflow taskflow; +taskflow.for_each_index(0, 100, 1, + [](){ + printf("%d\n", i); + }, + tf::RandomPartitioner(0, [](auto&& closure){ + // do something before invoking the partitioned task + // ... + + // invoke the partitioned task + closure(); + + // do something else after invoking the partitioned task + // ... + } +); +executor.run(taskflow).wait(); +@endcode */ -class RandomPartitioner : public PartitionerBase { +template +class RandomPartitioner : public PartitionerBase { public: + + /** + @brief queries the partition type (dynamic) + */ + static constexpr PartitionerType type() { return PartitionerType::DYNAMIC; } /** @brief default constructor @@ -412,14 +670,29 @@ class RandomPartitioner : public PartitionerBase { RandomPartitioner() = default; /** - @brief constructs a random partitioner + @brief construct a dynamic partitioner with the given chunk size */ - RandomPartitioner(size_t cz) : PartitionerBase(cz) {} + explicit RandomPartitioner(size_t sz) : PartitionerBase(sz) {} + + /** + @brief construct a random partitioner with the given chunk size and the closure + */ + explicit RandomPartitioner(size_t sz, C&& closure) : + PartitionerBase(sz, std::forward(closure)) { + } /** @brief constructs a random partitioner with the given parameters */ - RandomPartitioner(float alpha, float beta) : _alpha {alpha}, _beta {beta} {} + RandomPartitioner(float alpha, float beta) : _alpha{alpha}, _beta{beta} {} + + /** + @brief constructs a random partitioner with the given parameters and the closure + */ + RandomPartitioner(float alpha, float beta, C&& closure) : + _alpha {alpha}, _beta {beta}, + PartitionerBase(0, std::forward(closure)) { + } /** @brief queries the @c alpha value @@ -463,10 +736,7 @@ class RandomPartitioner : public PartitionerBase { std::enable_if_t, void>* = nullptr > void loop( - size_t N, - size_t W, - std::atomic& next, - F&& func + size_t N, size_t W, std::atomic& next, F&& func ) const { auto [b1, b2] = chunk_size_range(N, W); @@ -491,10 +761,7 @@ class RandomPartitioner : public PartitionerBase { std::enable_if_t, void>* = nullptr > void loop_until( - size_t N, - size_t W, - std::atomic& next, - F&& func + size_t N, size_t W, std::atomic& next, F&& func ) const { auto [b1, b2] = chunk_size_range(N, W); @@ -518,7 +785,6 @@ class RandomPartitioner : public PartitionerBase { float _alpha {0.01f}; float _beta {0.5f}; - }; /** @@ -527,15 +793,15 @@ class RandomPartitioner : public PartitionerBase { Guided partitioner can achieve decent performance for most parallel algorithms, especially for those with irregular and unbalanced workload per iteration. */ -using DefaultPartitioner = GuidedPartitioner; +using DefaultPartitioner = GuidedPartitioner<>; /** @brief determines if a type is a partitioner A partitioner is a derived type from tf::PartitionerBase. */ -template -inline constexpr bool is_partitioner_v = std::is_base_of::value; +template +inline constexpr bool is_partitioner_v = std::is_base_of::value; } // end of namespace tf ----------------------------------------------------- diff --git a/bundled/taskflow-3.6.0/include/taskflow/algorithm/pipeline.hpp b/bundled/taskflow-3.7.0/taskflow/algorithm/pipeline.hpp similarity index 93% rename from bundled/taskflow-3.6.0/include/taskflow/algorithm/pipeline.hpp rename to bundled/taskflow-3.7.0/taskflow/algorithm/pipeline.hpp index 5442d56006..79689d0879 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/algorithm/pipeline.hpp +++ b/bundled/taskflow-3.7.0/taskflow/algorithm/pipeline.hpp @@ -1078,6 +1078,7 @@ class ScalablePipeline { std::atomic join_counter; }; + public: /** @@ -1282,6 +1283,7 @@ ScalablePipeline

::ScalablePipeline(size_t num_lines, P first, P last) : _build(); } +/* // move constructor template ScalablePipeline

::ScalablePipeline(ScalablePipeline&& rhs) : @@ -1298,23 +1300,99 @@ ScalablePipeline

::ScalablePipeline(ScalablePipeline&& rhs) : rhs._longest_deferral = 0; rhs._num_tokens = 0; + std::cout << "scalable move constructor\n"; +} +*/ + +// move constructor +template +ScalablePipeline

::ScalablePipeline(ScalablePipeline&& rhs): + _num_tokens {rhs._num_tokens}, + _pipes {std::move(rhs._pipes)}, + _pipeflows {std::move(rhs._pipeflows)}, + _lines {std::move(rhs._lines)}, + _ready_tokens {std::move(rhs._ready_tokens)}, + _token_dependencies {std::move(rhs._token_dependencies)}, + _deferred_tokens {std::move(rhs._deferred_tokens)}, + _longest_deferral {rhs._longest_deferral}{ + + + //_num_tokens = rhs._num_tokens; + + //_pipes.resize(rhs.num_pipes()); + //size_t i=0; + //for(auto itr = rhs._pipes.begin(); itr != rhs._pipes.end(); itr++) { + // _pipes[i++] = *itr; + //} + + + //_pipeflows.resize(rhs.num_lines()); + //for(size_t l = 0; l(rhs.num_lines() * rhs._pipes.size()); + //for(size_t l=0; l +//ScalablePipeline

& ScalablePipeline

::operator = (ScalablePipeline&& rhs) { +// _graph = std::move(rhs._graph); +// _num_tokens = rhs._num_tokens; +// _pipes = std::move(rhs._pipes); +// _tasks = std::move(rhs._tasks); +// _pipeflows = std::move(rhs._pipeflows); +// _lines = std::move(rhs._lines); +// rhs._num_tokens = 0; +// _ready_tokens = std::move(rhs._ready_tokens); +// _token_dependencies = std::move(rhs._token_dependencies); +// _deferred_tokens = std::move(rhs._deferred_tokens); +// _longest_deferral = rhs._longest_deferral; +// rhs._longest_deferral = 0; +// std::cout << "scalable move assignment\n"; +// return *this; +//} + // move assignment operator template ScalablePipeline

& ScalablePipeline

::operator = (ScalablePipeline&& rhs) { - _graph = std::move(rhs._graph); - _num_tokens = rhs._num_tokens; - _pipes = std::move(rhs._pipes); - _tasks = std::move(rhs._tasks); - _pipeflows = std::move(rhs._pipeflows); - _lines = std::move(rhs._lines); - rhs._num_tokens = 0; - _ready_tokens = std::move(rhs._ready_tokens); - _token_dependencies = std::move(rhs._token_dependencies); - _deferred_tokens = std::move(rhs._deferred_tokens); - _longest_deferral = rhs._longest_deferral; + _num_tokens = rhs._num_tokens; + _pipes = std::move(rhs._pipes); + _pipeflows = std::move(rhs._pipeflows); + _lines = std::move(rhs._lines); + _ready_tokens = std::move(rhs._ready_tokens); + _token_dependencies = std::move(rhs._token_dependencies); + _deferred_tokens = std::move(rhs._deferred_tokens); + _longest_deferral = rhs._longest_deferral; + + _graph.clear(); + _tasks.resize(_pipeflows.size()+1); + rhs._longest_deferral = 0; + rhs._num_tokens = 0; + rhs._tasks.clear(); + _build(); return *this; } @@ -1525,7 +1603,7 @@ void ScalablePipeline

::_build() { using namespace std::literals::string_literals; FlowBuilder fb(_graph); - + // init task _tasks[0] = fb.emplace([this]() { return static_cast(_num_tokens % num_lines()); @@ -1533,7 +1611,7 @@ void ScalablePipeline

::_build() { // line task for(size_t l = 0; l < num_lines(); l++) { - + _tasks[l + 1] = fb.emplace([this, l] (tf::Runtime& rt) mutable { auto pf = &_pipeflows[l]; @@ -1598,11 +1676,11 @@ void ScalablePipeline

::_build() { else { _on_pipe(*pf, rt); } - + size_t c_f = pf->_pipe; size_t n_f = (pf->_pipe + 1) % num_pipes(); size_t n_l = (pf->_line + 1) % num_lines(); - + pf->_pipe = n_f; // ---- scheduling starts here ---- diff --git a/bundled/taskflow-3.7.0/taskflow/algorithm/reduce.hpp b/bundled/taskflow-3.7.0/taskflow/algorithm/reduce.hpp new file mode 100644 index 0000000000..b280934dff --- /dev/null +++ b/bundled/taskflow-3.7.0/taskflow/algorithm/reduce.hpp @@ -0,0 +1,434 @@ +#pragma once + +#include "launch.hpp" + +namespace tf { + +// Function: make_reduce_task +template +auto make_reduce_task(B b, E e, T& init, O bop, P part = P()) { + + using B_t = std::decay_t>; + using E_t = std::decay_t>; + + return [=, &r=init] (Runtime& rt) mutable { + + // fetch the iterator values + B_t beg = b; + E_t end = e; + + size_t W = rt.executor().num_workers(); + size_t N = std::distance(beg, end); + + // only myself - no need to spawn another graph + if(W <= 1 || N <= part.chunk_size()) { + launch_loop(part, [&](){ + for(; beg!=end; r = bop(r, *beg++)); + }); + return; + } + + if(N < W) { + W = N; + } + + std::mutex mtx; + + // static partitioner + if constexpr(part.type() == PartitionerType::STATIC) { + + size_t chunk_size; + + for(size_t w=0, curr_b=0; w lock(mtx); + r = bop(r, *beg); + return; + } + + auto beg1 = beg++; + auto beg2 = beg++; + T sum = bop(*beg1, *beg2); + + // loop reduce + part.loop(N, W, curr_b, chunk_size, + [&, prev_e=curr_b+2](size_t part_b, size_t part_e) mutable { + + if(part_b > prev_e) { + std::advance(beg, part_b - prev_e); + } + else { + part_b = prev_e; + } + + for(size_t x=part_b; x lock(mtx); + r = bop(r, sum); + }); + } + rt.corun_all(); + } + // dynamic partitioner + else { + std::atomic next(0); + launch_loop(N, W, rt, next, part, [=, &bop, &mtx, &next, &r, &part] () mutable { + // pre-reduce + size_t s0 = next.fetch_add(2, std::memory_order_relaxed); + + if(s0 >= N) { + return; + } + + std::advance(beg, s0); + + if(N - s0 == 1) { + std::lock_guard lock(mtx); + r = bop(r, *beg); + return; + } + + auto beg1 = beg++; + auto beg2 = beg++; + + T sum = bop(*beg1, *beg2); + + // loop reduce + part.loop(N, W, next, + [&, prev_e=s0+2](size_t curr_b, size_t curr_e) mutable { + std::advance(beg, curr_b - prev_e); + for(size_t x=curr_b; x lock(mtx); + r = bop(r, sum); + }); + } + }; +} + +// Function: make_transform_reduce_task +template < + typename B, typename E, typename T, typename BOP, typename UOP, + typename P = DefaultPartitioner +> +auto make_transform_reduce_task(B b, E e, T& init, BOP bop, UOP uop, P part = P()) { + + using B_t = std::decay_t>; + using E_t = std::decay_t>; + + return [=, &r=init] (Runtime& rt) mutable { + + // fetch the iterator values + B_t beg = b; + E_t end = e; + + size_t W = rt.executor().num_workers(); + size_t N = std::distance(beg, end); + + // only myself - no need to spawn another graph + if(W <= 1 || N <= part.chunk_size()) { + launch_loop(part, [&](){ + for(; beg!=end; r = bop(std::move(r), uop(*beg++))); + }); + return; + } + + if(N < W) { + W = N; + } + + std::mutex mtx; + + // static partitioner + if constexpr(part.type() == PartitionerType::STATIC) { + + size_t chunk_size; + + for(size_t w=0, curr_b=0; w lock(mtx); + r = bop(std::move(r), uop(*beg)); + return; + } + + //auto beg1 = beg++; + //auto beg2 = beg++; + //T sum = bop(uop(*beg1), uop(*beg2)); + + T sum = (chunk_size == 1) ? uop(*beg++) : bop(uop(*beg++), uop(*beg++)); + + // loop reduce + part.loop(N, W, curr_b, chunk_size, + [&, prev_e=curr_b+(chunk_size == 1 ? 1 : 2)] + (size_t part_b, size_t part_e) mutable { + if(part_b > prev_e) { + std::advance(beg, part_b - prev_e); + } + else { + part_b = prev_e; + } + for(size_t x=part_b; x lock(mtx); + r = bop(std::move(r), std::move(sum)); + }); + } + + rt.corun_all(); + } + // dynamic partitioner + else { + std::atomic next(0); + + launch_loop(N, W, rt, next, part, [=, &bop, &uop, &mtx, &next, &r, &part] () mutable { + // pre-reduce + size_t s0 = next.fetch_add(2, std::memory_order_relaxed); + + if(s0 >= N) { + return; + } + + std::advance(beg, s0); + + if(N - s0 == 1) { + std::lock_guard lock(mtx); + r = bop(std::move(r), uop(*beg)); + return; + } + + auto beg1 = beg++; + auto beg2 = beg++; + + T sum = bop(uop(*beg1), uop(*beg2)); + + // loop reduce + part.loop(N, W, next, + [&, prev_e=s0+2](size_t curr_b, size_t curr_e) mutable { + std::advance(beg, curr_b - prev_e); + for(size_t x=curr_b; x lock(mtx); + r = bop(std::move(r), std::move(sum)); + }); + } + }; +} + +// Function: make_transform_reduce_task with two binary operation +template < + typename B1, typename E1, typename B2, typename T, typename BOP_R, typename BOP_T, + typename P = DefaultPartitioner, + std::enable_if_t>, void>* = nullptr +> +auto make_transform_reduce_task( + B1 b1, E1 e1, B2 b2, T& init, BOP_R bop_r, BOP_T bop_t, P part = P() +) { + + using B1_t = std::decay_t>; + using E1_t = std::decay_t>; + using B2_t = std::decay_t>; + + return [=, &r=init] (Runtime& rt) mutable { + + // fetch the iterator values + B1_t beg1 = b1; + E1_t end1 = e1; + B2_t beg2 = b2; + + size_t W = rt.executor().num_workers(); + size_t N = std::distance(beg1, end1); + + // only myself - no need to spawn another graph + if(W <= 1 || N <= part.chunk_size()) { + launch_loop(part, [&](){ + for(; beg1!=end1; r = bop_r(std::move(r), bop_t(*beg1++, *beg2++))); + }); + return; + } + + if(N < W) { + W = N; + } + + std::mutex mtx; + + // static partitioner + if constexpr(part.type() == PartitionerType::STATIC) { + + size_t chunk_size; + + for(size_t w=0, curr_b=0; w lock(mtx); + r = bop_r(std::move(r), bop_t(*beg1, *beg2)); + return; + } + + T sum = (chunk_size == 1) ? bop_t(*beg1++, *beg2++) : + bop_r(bop_t(*beg1++, *beg2++), bop_t(*beg1++, *beg2++)); + + // loop reduce + part.loop(N, W, curr_b, chunk_size, + [&, prev_e=curr_b+(chunk_size == 1 ? 1 : 2)] + (size_t part_b, size_t part_e) mutable { + if(part_b > prev_e) { + std::advance(beg1, part_b - prev_e); + std::advance(beg2, part_b - prev_e); + } + else { + part_b = prev_e; + } + for(size_t x=part_b; x lock(mtx); + r = bop_r(std::move(r), std::move(sum)); + }); + } + + rt.corun_all(); + } + // dynamic partitioner + else { + std::atomic next(0); + + launch_loop(N, W, rt, next, part, [=, &bop_r, &bop_t, &mtx, &next, &r, &part] () mutable { + // pre-reduce + size_t s0 = next.fetch_add(2, std::memory_order_relaxed); + + if(s0 >= N) { + return; + } + + std::advance(beg1, s0); + std::advance(beg2, s0); + + if(N - s0 == 1) { + std::lock_guard lock(mtx); + r = bop_r(std::move(r), bop_t(*beg1, *beg2)); + return; + } + + auto beg11 = beg1++; + auto beg12 = beg1++; + auto beg21 = beg2++; + auto beg22 = beg2++; + + T sum = bop_r(bop_t(*beg11, *beg21), bop_t(*beg12, *beg22)); + + // loop reduce + part.loop(N, W, next, + [&, prev_e=s0+2](size_t curr_b, size_t curr_e) mutable { + std::advance(beg1, curr_b - prev_e); + std::advance(beg2, curr_b - prev_e); + for(size_t x=curr_b; x lock(mtx); + r = bop_r(std::move(r), std::move(sum)); + }); + } + }; +} + +// ---------------------------------------------------------------------------- +// default reduction +// ---------------------------------------------------------------------------- + +// Function: reduce +template +Task FlowBuilder::reduce(B beg, E end, T& init, O bop, P part) { + return emplace(make_reduce_task(beg, end, init, bop, part)); +} + +// ---------------------------------------------------------------------------- +// default transform and reduction +// ---------------------------------------------------------------------------- + +// Function: transform_reduce +template >, void>* +> +Task FlowBuilder::transform_reduce( + B beg, E end, T& init, BOP bop, UOP uop, P part +) { + return emplace(make_transform_reduce_task( + beg, end, init, bop, uop, part + )); +} + +// Function: transform_reduce +template < + typename B1, typename E1, typename B2, typename T, typename BOP_R, typename BOP_T, + typename P, + std::enable_if_t>, void>* +> +Task FlowBuilder::transform_reduce( + B1 beg1, E1 end1, B2 beg2, T& init, BOP_R bop_r, BOP_T bop_t, P part +) { + return emplace(make_transform_reduce_task( + beg1, end1, beg2, init, bop_r, bop_t, part + )); +} + +} // end of namespace tf ----------------------------------------------------- + + + + diff --git a/bundled/taskflow-3.6.0/include/taskflow/algorithm/scan.hpp b/bundled/taskflow-3.7.0/taskflow/algorithm/scan.hpp similarity index 76% rename from bundled/taskflow-3.6.0/include/taskflow/algorithm/scan.hpp rename to bundled/taskflow-3.7.0/taskflow/algorithm/scan.hpp index cccb2057dd..c1682126b7 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/algorithm/scan.hpp +++ b/bundled/taskflow-3.7.0/taskflow/algorithm/scan.hpp @@ -8,11 +8,11 @@ namespace detail { // Function: scan_loop template -TF_FORCE_INLINE void scan_loop( +void scan_loop( tf::Runtime& rt, std::atomic& counter, BufferT& buf, - B&& bop, + B bop, Iterator d_beg, size_t W, size_t w, @@ -42,15 +42,21 @@ TF_FORCE_INLINE void scan_loop( } } +} // end of namespace tf::detail --------------------------------------------- + + // Function: make_inclusive_scan_task -template -TF_FORCE_INLINE auto make_inclusive_scan_task(B first, E last, D d_first, BOP bop) { +template >, void>* = nullptr +> +auto make_inclusive_scan_task( + B first, E last, D d_first, BOP bop, P part = P() +) { using B_t = std::decay_t>; using E_t = std::decay_t>; using D_t = std::decay_t>; using value_type = typename std::iterator_traits::value_type; - using namespace std::string_literals; return [=] (Runtime& rt) mutable { @@ -68,7 +74,9 @@ TF_FORCE_INLINE auto make_inclusive_scan_task(B first, E last, D d_first, BOP bo // only myself - no need to spawn another graph if(W <= 1 || N <= 2) { - std::inclusive_scan(s_beg, s_end, d_beg, bop); + launch_loop(part, [&](){ + std::inclusive_scan(s_beg, s_end, d_beg, bop); + }); return; } @@ -90,8 +98,7 @@ TF_FORCE_INLINE auto make_inclusive_scan_task(B first, E last, D d_first, BOP bo chunk_size = std::min(Q + (w < R), N - curr_b); // block scan - launch_loop(W, w, rt, [=, &rt, &bop, &buf, &counter] () mutable { - + launch_loop(W, w, rt, part, [=, &rt, &bop, &buf, &counter] () mutable { auto result = d_beg; // local scan per worker @@ -134,19 +141,22 @@ TF_FORCE_INLINE auto make_inclusive_scan_task(B first, E last, D d_first, BOP bo curr_b += chunk_size; } - rt.join(); + rt.corun_all(); }; } // Function: make_inclusive_scan_task -template -TF_FORCE_INLINE auto make_inclusive_scan_task(B first, E last, D d_first, BOP bop, T init) { +template >, void>* = nullptr +> +auto make_inclusive_scan_task( + B first, E last, D d_first, BOP bop, T init, P part = P() +) { using B_t = std::decay_t>; using E_t = std::decay_t>; using D_t = std::decay_t>; using value_type = typename std::iterator_traits::value_type; - using namespace std::string_literals; return [=] (Runtime& rt) mutable { @@ -164,7 +174,9 @@ TF_FORCE_INLINE auto make_inclusive_scan_task(B first, E last, D d_first, BOP bo // only myself - no need to spawn another graph if(W <= 1 || N <= 2) { - std::inclusive_scan(s_beg, s_end, d_beg, bop, init); + launch_loop(part, [&](){ + std::inclusive_scan(s_beg, s_end, d_beg, bop, init); + }); return; } @@ -186,16 +198,15 @@ TF_FORCE_INLINE auto make_inclusive_scan_task(B first, E last, D d_first, BOP bo chunk_size = std::min(Q + (w < R), N - curr_b); // block scan - launch_loop(W, w, rt, [=, &rt, &bop, &buf, &counter] () mutable { - + launch_loop(W, w, rt, part, [=, &rt, &bop, &buf, &counter] () mutable { auto result = d_beg; // local scan per worker - auto& init = buf[w].data; - *d_beg++ = init = (w == 0) ? bop(init, *s_beg++) : *s_beg++; + auto& local = buf[w].data; + *d_beg++ = local = (w == 0) ? bop(local, *s_beg++) : *s_beg++; for(size_t i=1; i -TF_FORCE_INLINE auto make_transform_inclusive_scan_task( - B first, E last, D d_first, BOP bop, UOP uop +template >, void>* = nullptr +> +auto make_transform_inclusive_scan_task( + B first, E last, D d_first, BOP bop, UOP uop, P part = P() ) { using B_t = std::decay_t>; using E_t = std::decay_t>; using D_t = std::decay_t>; using value_type = typename std::iterator_traits::value_type; - using namespace std::string_literals; return [=] (Runtime& rt) mutable { @@ -243,7 +255,9 @@ TF_FORCE_INLINE auto make_transform_inclusive_scan_task( // only myself - no need to spawn another graph if(W <= 1 || N <= 2) { - std::transform_inclusive_scan(s_beg, s_end, d_beg, bop, uop); + launch_loop(part, [&](){ + std::transform_inclusive_scan(s_beg, s_end, d_beg, bop, uop); + }); return; } @@ -262,8 +276,7 @@ TF_FORCE_INLINE auto make_transform_inclusive_scan_task( chunk_size = std::min(Q + (w < R), N - curr_b); // block scan - launch_loop(W, w, rt, [=, &rt, &bop, &uop, &buf, &counter] () mutable { - + launch_loop(W, w, rt, part, [=, &rt, &bop, &uop, &buf, &counter] () mutable { auto result = d_beg; // local scan per worker @@ -283,21 +296,22 @@ TF_FORCE_INLINE auto make_transform_inclusive_scan_task( curr_b += chunk_size; } - rt.join(); + rt.corun_all(); }; } // Function: transform_inclusive_scan -template -TF_FORCE_INLINE auto make_transform_inclusive_scan_task( - B first, E last, D d_first, BOP bop, UOP uop, T init +template >, void>* = nullptr +> +auto make_transform_inclusive_scan_task( + B first, E last, D d_first, BOP bop, UOP uop, T init, P part = P() ) { using B_t = std::decay_t>; using E_t = std::decay_t>; using D_t = std::decay_t>; using value_type = typename std::iterator_traits::value_type; - using namespace std::string_literals; return [=] (Runtime& rt) mutable { @@ -315,7 +329,9 @@ TF_FORCE_INLINE auto make_transform_inclusive_scan_task( // only myself - no need to spawn another graph if(W <= 1 || N <= 2) { - std::transform_inclusive_scan(s_beg, s_end, d_beg, bop, uop, init); + launch_loop(part, [&](){ + std::transform_inclusive_scan(s_beg, s_end, d_beg, bop, uop, init); + }); return; } @@ -337,16 +353,15 @@ TF_FORCE_INLINE auto make_transform_inclusive_scan_task( chunk_size = std::min(Q + (w < R), N - curr_b); // block scan - launch_loop(W, w, rt, [=, &rt, &bop, &uop, &buf, &counter] () mutable { - + launch_loop(W, w, rt, part, [=, &rt, &bop, &uop, &buf, &counter] () mutable { auto result = d_beg; // local scan per worker - auto& init = buf[w].data; - *d_beg++ = init = (w == 0) ? bop(init, uop(*s_beg++)) : uop(*s_beg++); + auto& local = buf[w].data; + *d_beg++ = local = (w == 0) ? bop(local, uop(*s_beg++)) : uop(*s_beg++); for(size_t i=1; i -TF_FORCE_INLINE auto make_exclusive_scan_task( - B first, E last, D d_first, T init, BOP bop +template +auto make_exclusive_scan_task( + B first, E last, D d_first, T init, BOP bop, P part = P() ) { using B_t = std::decay_t>; using E_t = std::decay_t>; using D_t = std::decay_t>; using value_type = typename std::iterator_traits::value_type; - using namespace std::string_literals; return [=] (Runtime& rt) mutable { @@ -395,7 +409,9 @@ TF_FORCE_INLINE auto make_exclusive_scan_task( // only myself - no need to spawn another graph if(W <= 1 || N <= 2) { - std::exclusive_scan(s_beg, s_end, d_beg, init, bop); + launch_loop(part, [&](){ + std::exclusive_scan(s_beg, s_end, d_beg, init, bop); + }); return; } @@ -423,19 +439,18 @@ TF_FORCE_INLINE auto make_exclusive_scan_task( chunk_size = std::min(Q + (w < R), N - curr_b); // block scan - launch_loop(W, w, rt, [=, &rt, &bop, &buf, &counter] () mutable { - + launch_loop(W, w, rt, part, [=, &rt, &bop, &buf, &counter] () mutable { auto result = d_beg; // local scan per worker - auto& init = buf[w].data; + auto& local = buf[w].data; for(size_t i=1; i -TF_FORCE_INLINE auto make_transform_exclusive_scan_task( - B first, E last, D d_first, T init, BOP bop, UOP uop +template +auto make_transform_exclusive_scan_task( + B first, E last, D d_first, T init, BOP bop, UOP uop, P part = P() ) { using B_t = std::decay_t>; using E_t = std::decay_t>; using D_t = std::decay_t>; using value_type = typename std::iterator_traits::value_type; - using namespace std::string_literals; return [=] (Runtime& rt) mutable { @@ -483,7 +497,9 @@ TF_FORCE_INLINE auto make_transform_exclusive_scan_task( // only myself - no need to spawn another graph if(W <= 1 || N <= 2) { - std::transform_exclusive_scan(s_beg, s_end, d_beg, init, bop, uop); + launch_loop(part, [&](){ + std::transform_exclusive_scan(s_beg, s_end, d_beg, init, bop, uop); + }); return; } @@ -511,19 +527,18 @@ TF_FORCE_INLINE auto make_transform_exclusive_scan_task( chunk_size = std::min(Q + (w < R), N - curr_b); // block scan - launch_loop(W, w, rt, [=, &rt, &bop, &uop, &buf, &counter] () mutable { - + launch_loop(W, w, rt, part, [=, &rt, &bop, &uop, &buf, &counter] () mutable { auto result = d_beg; // local scan per worker - auto& init = buf[w].data; + auto& local = buf[w].data; for(size_t i=1; i -Task FlowBuilder::inclusive_scan(B first, E last, D d_first, BOP bop) { - return emplace(detail::make_inclusive_scan_task( - first, last, d_first, bop - )); +template >, void>* +> +Task FlowBuilder::inclusive_scan(B first, E last, D d_first, BOP bop, P part) { + return emplace(make_inclusive_scan_task(first, last, d_first, bop, part)); } // Function: inclusive_scan -template -Task FlowBuilder::inclusive_scan(B first, E last, D d_first, BOP bop, T init) { - return emplace(detail::make_inclusive_scan_task( - first, last, d_first, bop, init - )); +template >, void>* +> +Task FlowBuilder::inclusive_scan(B first, E last, D d_first, BOP bop, T init, P part) { + return emplace(make_inclusive_scan_task(first, last, d_first, bop, init, part)); } // ---------------------------------------------------------------------------- @@ -566,22 +580,26 @@ Task FlowBuilder::inclusive_scan(B first, E last, D d_first, BOP bop, T init) { // ---------------------------------------------------------------------------- // Function: transform_inclusive_scan -template +template >, void>* +> Task FlowBuilder::transform_inclusive_scan( - B first, E last, D d_first, BOP bop, UOP uop + B first, E last, D d_first, BOP bop, UOP uop, P part ) { - return emplace(detail::make_transform_inclusive_scan_task( - first, last, d_first, bop, uop + return emplace(make_transform_inclusive_scan_task( + first, last, d_first, bop, uop, part )); } // Function: transform_inclusive_scan -template +template >, void>* +> Task FlowBuilder::transform_inclusive_scan( - B first, E last, D d_first, BOP bop, UOP uop, T init + B first, E last, D d_first, BOP bop, UOP uop, T init, P part ) { - return emplace(detail::make_transform_inclusive_scan_task( - first, last, d_first, bop, uop, init + return emplace(make_transform_inclusive_scan_task( + first, last, d_first, bop, uop, init, part )); } @@ -590,10 +608,10 @@ Task FlowBuilder::transform_inclusive_scan( // ---------------------------------------------------------------------------- // Function: exclusive_scan -template -Task FlowBuilder::exclusive_scan(B first, E last, D d_first, T init, BOP bop) { - return emplace(detail::make_exclusive_scan_task( - first, last, d_first, init, bop +template +Task FlowBuilder::exclusive_scan(B first, E last, D d_first, T init, BOP bop, P part) { + return emplace(make_exclusive_scan_task( + first, last, d_first, init, bop, part )); } @@ -602,13 +620,14 @@ Task FlowBuilder::exclusive_scan(B first, E last, D d_first, T init, BOP bop) { // ---------------------------------------------------------------------------- // Function: transform_exclusive_scan -template +template Task FlowBuilder::transform_exclusive_scan( - B first, E last, D d_first, T init, BOP bop, UOP uop + B first, E last, D d_first, T init, BOP bop, UOP uop, P part ) { - return emplace(detail::make_transform_exclusive_scan_task( - first, last, d_first, init, bop, uop + return emplace(make_transform_exclusive_scan_task( + first, last, d_first, init, bop, uop, part )); } } // end of namespace tf ----------------------------------------------------- + diff --git a/bundled/taskflow-3.6.0/include/taskflow/algorithm/sort.hpp b/bundled/taskflow-3.7.0/taskflow/algorithm/sort.hpp similarity index 96% rename from bundled/taskflow-3.6.0/include/taskflow/algorithm/sort.hpp rename to bundled/taskflow-3.7.0/taskflow/algorithm/sort.hpp index a4fdf3ce76..4460f8f4a3 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/algorithm/sort.hpp +++ b/bundled/taskflow-3.7.0/taskflow/algorithm/sort.hpp @@ -2,7 +2,7 @@ #include "../core/async.hpp" -namespace tf { +namespace tf::detail { // threshold whether or not to perform parallel sort template @@ -595,15 +595,15 @@ void parallel_3wqsort(tf::Runtime& rt, RandItr first, RandItr last, C compare) { //rt.join(); } -// ---------------------------------------------------------------------------- -// tf::Taskflow::sort -// ---------------------------------------------------------------------------- +} // end of namespace tf::detail --------------------------------------------- -// Function: sort -template -Task FlowBuilder::sort(B beg, E end, C cmp) { +namespace tf { - Task task = emplace([b=beg, e=end, cmp] (Runtime& rt) mutable { +// Function: make_sort_task +template +TF_FORCE_INLINE auto make_sort_task(B b, E e, C cmp) { + + return [b, e, cmp] (Runtime& rt) mutable { using B_t = std::decay_t>; using E_t = std::decay_t>; @@ -616,32 +616,45 @@ Task FlowBuilder::sort(B beg, E end, C cmp) { return; } - size_t W = rt._executor.num_workers(); + size_t W = rt.executor().num_workers(); size_t N = std::distance(beg, end); // only myself - no need to spawn another graph - if(W <= 1 || N <= parallel_sort_cutoff()) { + if(W <= 1 || N <= detail::parallel_sort_cutoff()) { std::sort(beg, end, cmp); return; } //parallel_3wqsort(rt, beg, end-1, cmp); - parallel_pdqsort> && std::is_arithmetic_v::value_type> >(rt, beg, end, cmp, log2(end - beg)); - rt.join(); - }); + rt.corun_all(); + }; +} + +template +TF_FORCE_INLINE auto make_sort_task(B beg, E end) { + using value_type = std::decay_t())>; + return make_sort_task(beg, end, std::less{}); +} + +// ---------------------------------------------------------------------------- +// tf::Taskflow::sort +// ---------------------------------------------------------------------------- - return task; +// Function: sort +template +Task FlowBuilder::sort(B beg, E end, C cmp) { + return emplace(make_sort_task(beg, end, cmp)); } // Function: sort template Task FlowBuilder::sort(B beg, E end) { - using value_type = std::decay_t())>; - return sort(beg, end, std::less{}); + return emplace(make_sort_task(beg, end)); } } // namespace tf ------------------------------------------------------------ diff --git a/bundled/taskflow-3.6.0/include/taskflow/algorithm/transform.hpp b/bundled/taskflow-3.7.0/taskflow/algorithm/transform.hpp similarity index 58% rename from bundled/taskflow-3.6.0/include/taskflow/algorithm/transform.hpp rename to bundled/taskflow-3.7.0/taskflow/algorithm/transform.hpp index 4c87887707..b155f658bc 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/algorithm/transform.hpp +++ b/bundled/taskflow-3.7.0/taskflow/algorithm/transform.hpp @@ -4,23 +4,18 @@ namespace tf { -namespace detail { - // Function: make_transform_task -template -TF_FORCE_INLINE auto make_transform_task( - B first1, E last1, O d_first, C c, P&& part -) { - - using namespace std::string_literals; +template < + typename B, typename E, typename O, typename C, typename P = DefaultPartitioner, + std::enable_if_t>, void>* = nullptr +> +auto make_transform_task(B first1, E last1, O d_first, C c, P part = P()) { using B_t = std::decay_t>; using E_t = std::decay_t>; using O_t = std::decay_t>; - return - [first1, last1, d_first, c, part=std::forward

(part)] - (Runtime& rt) mutable { + return [=] (Runtime& rt) mutable { // fetch the stateful values B_t beg = first1; @@ -32,7 +27,9 @@ TF_FORCE_INLINE auto make_transform_task( // only myself - no need to spawn another graph if(W <= 1 || N <= part.chunk_size()) { - std::transform(beg, end, d_beg, c); + launch_loop(part, [&](){ + std::transform(beg, end, d_beg, c); + }); return; } @@ -41,38 +38,37 @@ TF_FORCE_INLINE auto make_transform_task( } // static partitioner - if constexpr(std::is_same_v, StaticPartitioner>) { + if constexpr(part.type() == PartitionerType::STATIC) { size_t chunk_size; for(size_t w=0, curr_b=0; w next(0); - launch_loop(N, W, rt, next, part, [=, &next, &part] () mutable { part.loop(N, W, next, - [&, prev_e=size_t{0}](size_t curr_b, size_t curr_e) mutable { - std::advance(beg, curr_b - prev_e); - std::advance(d_beg, curr_b - prev_e); - for(size_t x = curr_b; x>, void>* = nullptr > -TF_FORCE_INLINE auto make_transform_task( - B1 first1, E1 last1, B2 first2, O d_first, C c, P&& part -) { - - using namespace std::string_literals; +auto make_transform_task(B1 first1, E1 last1, B2 first2, O d_first, C c, P part = P()) { using B1_t = std::decay_t>; using E1_t = std::decay_t>; using B2_t = std::decay_t>; using O_t = std::decay_t>; - return - [first1, last1, first2, d_first, c, part=std::forward

(part)] - (Runtime& rt) mutable { + return [=] (Runtime& rt) mutable { // fetch the stateful values B1_t beg1 = first1; @@ -111,7 +101,9 @@ TF_FORCE_INLINE auto make_transform_task( // only myself - no need to spawn another graph if(W <= 1 || N <= part.chunk_size()) { - std::transform(beg1, end1, beg2, d_beg, c); + launch_loop(part, [&](){ + std::transform(beg1, end1, beg2, d_beg, c); + }); return; } @@ -120,57 +112,57 @@ TF_FORCE_INLINE auto make_transform_task( } // static partitioner - if constexpr(std::is_same_v, StaticPartitioner>) { + if constexpr(part.type() == PartitionerType::STATIC) { size_t chunk_size; for(size_t w=0, curr_b=0; w next(0); launch_loop(N, W, rt, next, part, [=, &c, &next, &part] () mutable { part.loop(N, W, next, - [&, prev_e=size_t{0}](size_t curr_b, size_t curr_e) mutable { - std::advance(beg1, curr_b - prev_e); - std::advance(beg2, curr_b - prev_e); - std::advance(d_beg, curr_b - prev_e); - for(size_t x = curr_b; x -Task FlowBuilder::transform(B first1, E last1, O d_first, C c, P&& part) { +template >, void>* +> +Task FlowBuilder::transform(B first1, E last1, O d_first, C c, P part) { return emplace( - detail::make_transform_task(first1, last1, d_first, c, std::forward

(part)) + make_transform_task(first1, last1, d_first, c, part) ); } @@ -184,11 +176,10 @@ template < std::enable_if_t>, void>* > Task FlowBuilder::transform( - B1 first1, E1 last1, B2 first2, O d_first, C c, P&& part + B1 first1, E1 last1, B2 first2, O d_first, C c, P part ) { - - return emplace(detail::make_transform_task( - first1, last1, first2, d_first, c, std::forward

(part) + return emplace(make_transform_task( + first1, last1, first2, d_first, c, part )); } diff --git a/bundled/taskflow-3.7.0/taskflow/core/async.hpp b/bundled/taskflow-3.7.0/taskflow/core/async.hpp new file mode 100644 index 0000000000..ed671f9208 --- /dev/null +++ b/bundled/taskflow-3.7.0/taskflow/core/async.hpp @@ -0,0 +1,331 @@ +#pragma once + +#include "executor.hpp" + +// https://hackmd.io/@sysprog/concurrency-atomics + +namespace tf { + +// ---------------------------------------------------------------------------- +// Async +// ---------------------------------------------------------------------------- + +// Function: async +template +auto Executor::async(P&& params, F&& f) { + + _increment_topology(); + + using R = std::invoke_result_t>; + + std::packaged_task p(std::forward(f)); + auto fu{p.get_future()}; + + auto node = node_pool.animate( + std::forward

(params), nullptr, nullptr, 0, + // handle + std::in_place_type_t{}, + [p=make_moc(std::move(p))]() mutable { p.object(); } + ); + + _schedule_async_task(node); + + return fu; +} + +// Function: async +template +auto Executor::async(F&& f) { + return async(DefaultTaskParams{}, std::forward(f)); +} + +// ---------------------------------------------------------------------------- +// Silent Async +// ---------------------------------------------------------------------------- + +// Function: silent_async +template +void Executor::silent_async(P&& params, F&& f) { + + _increment_topology(); + + auto node = node_pool.animate( + std::forward

(params), nullptr, nullptr, 0, + // handle + std::in_place_type_t{}, std::forward(f) + ); + + _schedule_async_task(node); +} + +// Function: silent_async +template +void Executor::silent_async(F&& f) { + silent_async(DefaultTaskParams{}, std::forward(f)); +} + +// ---------------------------------------------------------------------------- +// Async Helper Methods +// ---------------------------------------------------------------------------- + +// Procedure: _schedule_async_task +inline void Executor::_schedule_async_task(Node* node) { + if(auto w = _this_worker(); w) { + _schedule(*w, node); + } + else{ + _schedule(node); + } +} + +// Procedure: _tear_down_async +inline void Executor::_tear_down_async(Node* node) { + // from runtime + if(node->_parent) { + node->_parent->_join_counter.fetch_sub(1, std::memory_order_release); + } + // from executor + else { + _decrement_topology(); + } + node_pool.recycle(node); +} + +// ---------------------------------------------------------------------------- +// Silent Dependent Async +// ---------------------------------------------------------------------------- + +// Function: silent_dependent_async +template ...>, void>* +> +tf::AsyncTask Executor::silent_dependent_async(F&& func, Tasks&&... tasks) { + return silent_dependent_async( + DefaultTaskParams{}, std::forward(func), std::forward(tasks)... + ); +} + +// Function: silent_dependent_async +template && all_same_v...>, void>* +> +tf::AsyncTask Executor::silent_dependent_async( + P&& params, F&& func, Tasks&&... tasks +){ + + _increment_topology(); + + size_t num_dependents = sizeof...(Tasks); + + // create a task before scheduling the node to retain a shared ownership first + AsyncTask task(node_pool.animate( + std::forward

(params), nullptr, nullptr, num_dependents, + std::in_place_type_t{}, std::forward(func) + )); + + if constexpr(sizeof...(Tasks) > 0) { + (_process_async_dependent(task._node, tasks, num_dependents), ...); + } + + if(num_dependents == 0) { + _schedule_async_task(task._node); + } + + return task; +} + +// Function: silent_dependent_async +template , AsyncTask>, void>* +> +tf::AsyncTask Executor::silent_dependent_async(F&& func, I first, I last) { + return silent_dependent_async(DefaultTaskParams{}, std::forward(func), first, last); +} + +// Function: silent_dependent_async +template && !std::is_same_v, AsyncTask>, void>* +> +tf::AsyncTask Executor::silent_dependent_async( + P&& params, F&& func, I first, I last +) { + + _increment_topology(); + + size_t num_dependents = std::distance(first, last); + + AsyncTask task(node_pool.animate( + std::forward

(params), nullptr, nullptr, num_dependents, + std::in_place_type_t{}, std::forward(func) + )); + + for(; first != last; first++){ + _process_async_dependent(task._node, *first, num_dependents); + } + + if(num_dependents == 0) { + _schedule_async_task(task._node); + } + + return task; +} + +// ---------------------------------------------------------------------------- +// Dependent Async +// ---------------------------------------------------------------------------- + +// Function: dependent_async +template ...>, void>* +> +auto Executor::dependent_async(F&& func, Tasks&&... tasks) { + return dependent_async(DefaultTaskParams{}, std::forward(func), std::forward(tasks)...); +} + +// Function: dependent_async +template && all_same_v...>, void>* +> +auto Executor::dependent_async(P&& params, F&& func, Tasks&&... tasks) { + + _increment_topology(); + + using R = std::invoke_result_t>; + + std::packaged_task p(std::forward(func)); + auto fu{p.get_future()}; + + size_t num_dependents = sizeof...(tasks); + + AsyncTask task(node_pool.animate( + std::forward

(params), nullptr, nullptr, num_dependents, + std::in_place_type_t{}, + [p=make_moc(std::move(p))] () mutable { p.object(); } + )); + + if constexpr(sizeof...(Tasks) > 0) { + (_process_async_dependent(task._node, tasks, num_dependents), ...); + } + + if(num_dependents == 0) { + _schedule_async_task(task._node); + } + + return std::make_pair(std::move(task), std::move(fu)); +} + +// Function: dependent_async +template , AsyncTask>, void>* +> +auto Executor::dependent_async(F&& func, I first, I last) { + return dependent_async(DefaultTaskParams{}, std::forward(func), first, last); +} + +// Function: dependent_async +template && !std::is_same_v, AsyncTask>, void>* +> +auto Executor::dependent_async(P&& params, F&& func, I first, I last) { + + _increment_topology(); + + using R = std::invoke_result_t>; + + std::packaged_task p(std::forward(func)); + auto fu{p.get_future()}; + + size_t num_dependents = std::distance(first, last); + + AsyncTask task(node_pool.animate( + std::forward

(params), nullptr, nullptr, num_dependents, + std::in_place_type_t{}, + [p=make_moc(std::move(p))] () mutable { p.object(); } + )); + + for(; first != last; first++) { + _process_async_dependent(task._node, *first, num_dependents); + } + + if(num_dependents == 0) { + _schedule_async_task(task._node); + } + + return std::make_pair(std::move(task), std::move(fu)); +} + +// ---------------------------------------------------------------------------- +// Dependent Async Helper Functions +// ---------------------------------------------------------------------------- + +// Procedure: _process_async_dependent +inline void Executor::_process_async_dependent( + Node* node, tf::AsyncTask& task, size_t& num_dependents +) { + + auto& state = std::get_if(&(task._node->_handle))->state; + + add_successor: + + auto target = Node::AsyncState::UNFINISHED; + + // acquires the lock + if(state.compare_exchange_weak(target, Node::AsyncState::LOCKED, + std::memory_order_acq_rel, + std::memory_order_acquire)) { + task._node->_successors.push_back(node); + state.store(Node::AsyncState::UNFINISHED, std::memory_order_release); + } + // dep's state is FINISHED, which means dep finished its callable already + // thus decrement the node's join counter by 1 + else if (target == Node::AsyncState::FINISHED) { + num_dependents = node->_join_counter.fetch_sub(1, std::memory_order_acq_rel) - 1; + } + // another worker adding its async task to the same successors of this node + else { + goto add_successor; + } +} + + +// Procedure: _tear_down_dependent_async +inline void Executor::_tear_down_dependent_async(Worker& worker, Node* node) { + + auto handle = std::get_if(&(node->_handle)); + + // this async task comes from Executor + auto target = Node::AsyncState::UNFINISHED; + + while(!handle->state.compare_exchange_weak(target, Node::AsyncState::FINISHED, + std::memory_order_acq_rel, + std::memory_order_relaxed)) { + target = Node::AsyncState::UNFINISHED; + } + + // spaw successors whenever their dependencies are resolved + worker._cache = nullptr; + for(size_t i=0; i_successors.size(); ++i) { + if(auto s = node->_successors[i]; + s->_join_counter.fetch_sub(1, std::memory_order_acq_rel) == 1 + ) { + if(worker._cache) { + _schedule(worker, worker._cache); + } + worker._cache = s; + } + } + + // now the executor no longer needs to retain ownership + if(handle->use_count.fetch_sub(1, std::memory_order_acq_rel) == 1) { + node_pool.recycle(node); + } + + _decrement_topology(); +} + + + + + +} // end of namespace tf ----------------------------------------------------- + diff --git a/bundled/taskflow-3.7.0/taskflow/core/async_task.hpp b/bundled/taskflow-3.7.0/taskflow/core/async_task.hpp new file mode 100644 index 0000000000..026e8cb1c7 --- /dev/null +++ b/bundled/taskflow-3.7.0/taskflow/core/async_task.hpp @@ -0,0 +1,209 @@ +#pragma once + +#include "graph.hpp" + +/** +@file async_task.hpp +@brief asynchronous task include file +*/ + +namespace tf { + +// ---------------------------------------------------------------------------- +// AsyncTask +// ---------------------------------------------------------------------------- + +/** +@brief class to create a dependent asynchronous task + +A tf::AsyncTask is a lightweight handle that retains @em shared ownership +of a dependent async task created by an executor. +This shared ownership ensures that the async task remains alive when +adding it to the dependency list of another async task, +thus avoiding the classical [ABA problem](https://en.wikipedia.org/wiki/ABA_problem). + +@code{.cpp} +// main thread retains shared ownership of async task A +tf::AsyncTask A = executor.silent_dependent_async([](){}); + +// task A remains alive (i.e., at least one ref count by the main thread) +// when being added to the dependency list of async task B +tf::AsyncTask B = executor.silent_dependent_async([](){}, A); +@endcode + +Currently, tf::AsyncTask is implemented based on the logic of +C++ smart pointer std::shared_ptr and +is considered cheap to copy or move as long as only a handful of objects +own it. +When a worker completes an async task, it will remove the task from the executor, +decrementing the number of shared owners by one. +If that counter reaches zero, the task is destroyed. +*/ +class AsyncTask { + + friend class Executor; + + public: + + /** + @brief constructs an empty task handle + */ + AsyncTask() = default; + + /** + @brief destroys the managed asynchronous task if this is the last owner + */ + ~AsyncTask(); + + /** + @brief constructs an asynchronous task that shares ownership of @c rhs + */ + AsyncTask(const AsyncTask& rhs); + + /** + @brief move-constructs an asynchronous task from @c rhs + */ + AsyncTask(AsyncTask&& rhs); + + /** + @brief copy-assigns the asynchronous task from @c rhs + + Releases the managed object of @c this and retains a new shared ownership + of @c rhs. + */ + AsyncTask& operator = (const AsyncTask& rhs); + + /** + @brief move-assigns the asynchronous task from @c rhs + + Releases the managed object of @c this and takes over the ownership of @c rhs. + */ + AsyncTask& operator = (AsyncTask&& rhs); + + /** + @brief checks if the asynchronous task stores nothing + */ + bool empty() const; + + /** + @brief release the managed object of @c this + */ + void reset(); + + /** + @brief obtains a hash value of this asynchronous task + */ + size_t hash_value() const; + + /** + @brief returns the number of shared owners that are currently managing + this asynchronous task + */ + size_t use_count() const; + + /** + @brief returns the boolean indicating whether the async task is done + */ + bool is_done() const; + + private: + + explicit AsyncTask(Node*); + + Node* _node {nullptr}; + + void _incref(); + void _decref(); +}; + +// Constructor +inline AsyncTask::AsyncTask(Node* ptr) : _node{ptr} { + _incref(); +} + +// Function: _incref +inline void AsyncTask::_incref() { + if(_node) { + std::get_if(&(_node->_handle))->use_count.fetch_add( + 1, std::memory_order_relaxed + ); + } +} + +// Function: _decref +inline void AsyncTask::_decref() { + if(_node && std::get_if(&(_node->_handle))->use_count.fetch_sub( + 1, std::memory_order_acq_rel + ) == 1) { + node_pool.recycle(_node); + } +} + +// Copy Constructor +inline AsyncTask::AsyncTask(const AsyncTask& rhs) : + _node{rhs._node} { + _incref(); +} + +// Move Constructor +inline AsyncTask::AsyncTask(AsyncTask&& rhs) : + _node {rhs._node} { + rhs._node = nullptr; +} + +// Destructor +inline AsyncTask::~AsyncTask() { + _decref(); +} + +// Copy assignment +inline AsyncTask& AsyncTask::operator = (const AsyncTask& rhs) { + _decref(); + _node = rhs._node; + _incref(); + return *this; +} + +// Move assignment +inline AsyncTask& AsyncTask::operator = (AsyncTask&& rhs) { + _decref(); + _node = rhs._node; + rhs._node = nullptr; + return *this; +} + +// Function: empty +inline bool AsyncTask::empty() const { + return _node == nullptr; +} + +// Function: reset +inline void AsyncTask::reset() { + _decref(); + _node = nullptr; +} + +// Function: hash_value +inline size_t AsyncTask::hash_value() const { + return std::hash{}(_node); +} + +// Function: use_count +inline size_t AsyncTask::use_count() const { + return _node == nullptr ? size_t{0} : + std::get_if(&(_node->_handle))->use_count.load( + std::memory_order_relaxed + ); +} + +// Function: is_done +inline bool AsyncTask::is_done() const { + return std::get_if(&(_node->_handle))->state.load( + std::memory_order_acquire + ) == Node::AsyncState::FINISHED; +} + +} // end of namespace tf ---------------------------------------------------- + + + diff --git a/bundled/taskflow-3.6.0/include/taskflow/core/declarations.hpp b/bundled/taskflow-3.7.0/taskflow/core/declarations.hpp similarity index 86% rename from bundled/taskflow-3.6.0/include/taskflow/core/declarations.hpp rename to bundled/taskflow-3.7.0/taskflow/core/declarations.hpp index dd89ab3b21..7763fab0ba 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/core/declarations.hpp +++ b/bundled/taskflow-3.7.0/taskflow/core/declarations.hpp @@ -52,6 +52,12 @@ class syclGraph; class syclTask; class syclFlow; +// ---------------------------------------------------------------------------- +// struct +// ---------------------------------------------------------------------------- +struct TaskParams; +struct DefaultTaskParams; + } // end of namespace tf ----------------------------------------------------- diff --git a/bundled/taskflow-3.6.0/include/taskflow/core/environment.hpp b/bundled/taskflow-3.7.0/taskflow/core/environment.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/core/environment.hpp rename to bundled/taskflow-3.7.0/taskflow/core/environment.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/core/error.hpp b/bundled/taskflow-3.7.0/taskflow/core/error.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/core/error.hpp rename to bundled/taskflow-3.7.0/taskflow/core/error.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/core/executor-module-opt.hpp b/bundled/taskflow-3.7.0/taskflow/core/executor-module-opt.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/core/executor-module-opt.hpp rename to bundled/taskflow-3.7.0/taskflow/core/executor-module-opt.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/core/executor.hpp b/bundled/taskflow-3.7.0/taskflow/core/executor.hpp similarity index 81% rename from bundled/taskflow-3.6.0/include/taskflow/core/executor.hpp rename to bundled/taskflow-3.7.0/taskflow/core/executor.hpp index a5607e044b..a9d9dc457d 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/core/executor.hpp +++ b/bundled/taskflow-3.7.0/taskflow/core/executor.hpp @@ -59,23 +59,15 @@ class Executor { /** @brief constructs the executor with @c N worker threads - - @param N number of workers (default std::thread::hardware_concurrency) - @param wix worker interface class to alter worker (thread) behaviors + @param N the number of workers (default std::thread::hardware_concurrency) The constructor spawns @c N worker threads to run tasks in a work-stealing loop. The number of workers must be greater than zero or an exception will be thrown. By default, the number of worker threads is equal to the maximum hardware concurrency returned by std::thread::hardware_concurrency. - - Users can alter the worker behavior, such as changing thread affinity, - via deriving an instance from tf::WorkerInterface. */ - explicit Executor( - size_t N = std::thread::hardware_concurrency(), - std::shared_ptr wix = nullptr - ); + explicit Executor(size_t N = std::thread::hardware_concurrency()); /** @brief destructs the executor @@ -599,23 +591,25 @@ class Executor { // -------------------------------------------------------------------------- // Async Task Methods // -------------------------------------------------------------------------- - + /** - @brief runs a given function asynchronously + @brief creates a parameterized asynchronous task to run the given function + @tparam P task parameter type @tparam F callable type + @param params task parameters @param func callable object @return a @std_future that will hold the result of the execution - - The method creates an asynchronous task to run the given function - and return a @std_future object that eventually will hold the result - of the return value. + + The method creates a parameterized asynchronous task + to run the given function and return a @std_future object + that eventually will hold the result of the execution. @code{.cpp} - std::future future = executor.async([](){ - std::cout << "create an asynchronous task and returns 1\n"; + std::future future = executor.async("name", [](){ + std::cout << "create an asynchronous task with a name and returns 1\n"; return 1; }); future.get(); @@ -623,27 +617,25 @@ class Executor { This member function is thread-safe. */ - template - auto async(F&& func); + template + auto async(P&& params, F&& func); /** - @brief runs a given function asynchronously and gives a name to this task + @brief runs a given function asynchronously @tparam F callable type - @param name name of the asynchronous task @param func callable object @return a @std_future that will hold the result of the execution - - The method creates and assigns a name to an asynchronous task - to run the given function, - returning @std_future object that eventually will hold the result - Assigned task names will appear in the observers of the executor. + + The method creates an asynchronous task to run the given function + and return a @std_future object that eventually will hold the result + of the return value. @code{.cpp} - std::future future = executor.async("name", [](){ - std::cout << "create an asynchronous task with a name and returns 1\n"; + std::future future = executor.async([](){ + std::cout << "create an asynchronous task and returns 1\n"; return 1; }); future.get(); @@ -652,47 +644,50 @@ class Executor { This member function is thread-safe. */ template - auto async(const std::string& name, F&& func); + auto async(F&& func); /** @brief similar to tf::Executor::async but does not return a future object - + @tparam F callable type - + + @param params task parameters @param func callable object - This member function is more efficient than tf::Executor::async - and is encouraged to use when you do not want a @std_future to - acquire the result or synchronize the execution. + The method creates a parameterized asynchronous task + to run the given function without returning any @std_future object. + This member function is more efficient than tf::Executor::async + and is encouraged to use when applications do not need a @std_future to acquire + the result or synchronize the execution. @code{.cpp} - executor.silent_async([](){ - std::cout << "create an asynchronous task with no return\n"; + executor.silent_async("name", [](){ + std::cout << "create an asynchronous task with a name and no return\n"; }); executor.wait_for_all(); @endcode This member function is thread-safe. */ - template - void silent_async(F&& func); - + template + void silent_async(P&& params, F&& func); + /** @brief similar to tf::Executor::async but does not return a future object - + @tparam F callable type - - @param name assigned name to the task + @param func callable object - This member function is more efficient than tf::Executor::async - and is encouraged to use when you do not want a @std_future to - acquire the result or synchronize the execution. - Assigned task names will appear in the observers of the executor. + The method creates an asynchronous task + to run the given function without returning any @std_future object. + This member function is more efficient than tf::Executor::async + and is encouraged to use when applications do not need a @std_future to acquire + the result or synchronize the execution. @code{.cpp} - executor.silent_async("name", [](){ - std::cout << "create an asynchronous task with a name and no return\n"; + executor.silent_async([](){ + std::cout << "create an asynchronous task with no return\n"; }); executor.wait_for_all(); @endcode @@ -700,7 +695,7 @@ class Executor { This member function is thread-safe. */ template - void silent_async(const std::string& name, F&& func); + void silent_async(F&& func); // -------------------------------------------------------------------------- // Silent Dependent Async Methods @@ -739,13 +734,13 @@ class Executor { tf::AsyncTask silent_dependent_async(F&& func, Tasks&&... tasks); /** - @brief names and runs the given function asynchronously + @brief runs the given function asynchronously when the given dependents finish @tparam F callable type @tparam Tasks task types convertible to tf::AsyncTask - @param name assigned name to the task + @param params task parameters @param func callable object @param tasks asynchronous tasks on which this execution depends @@ -769,10 +764,10 @@ class Executor { This member function is thread-safe. */ - template ...>, void>* = nullptr + template && all_same_v...>, void>* = nullptr > - tf::AsyncTask silent_dependent_async(const std::string& name, F&& func, Tasks&&... tasks); + tf::AsyncTask silent_dependent_async(P&& params, F&& func, Tasks&&... tasks); /** @brief runs the given function asynchronously @@ -812,13 +807,13 @@ class Executor { tf::AsyncTask silent_dependent_async(F&& func, I first, I last); /** - @brief names and runs the given function asynchronously + @brief runs the given function asynchronously when the given range of dependents finish @tparam F callable type @tparam I iterator type - @param name assigned name to the task + @param params tasks parameters @param func callable object @param first iterator to the beginning (inclusive) @param last iterator to the end (exclusive) @@ -845,10 +840,10 @@ class Executor { This member function is thread-safe. */ - template , AsyncTask>, void>* = nullptr + template && !std::is_same_v, AsyncTask>, void>* = nullptr > - tf::AsyncTask silent_dependent_async(const std::string& name, F&& func, I first, I last); + tf::AsyncTask silent_dependent_async(P&& params, F&& func, I first, I last); // -------------------------------------------------------------------------- // Dependent Async Methods @@ -897,13 +892,14 @@ class Executor { auto dependent_async(F&& func, Tasks&&... tasks); /** - @brief names and runs the given function asynchronously + @brief runs the given function asynchronously when the given dependents finish + @tparam P task parameters type @tparam F callable type @tparam Tasks task types convertible to tf::AsyncTask - @param name assigned name to the task + @param params task parameters @param func callable object @param tasks asynchronous tasks on which this execution depends @@ -936,10 +932,10 @@ class Executor { This member function is thread-safe. */ - template ...>, void>* = nullptr + template && all_same_v...>, void>* = nullptr > - auto dependent_async(const std::string& name, F&& func, Tasks&&... tasks); + auto dependent_async(P&& params, F&& func, Tasks&&... tasks); /** @brief runs the given function asynchronously @@ -987,13 +983,14 @@ class Executor { auto dependent_async(F&& func, I first, I last); /** - @brief names and runs the given function asynchronously + @brief runs the given function asynchronously when the given range of dependents finish + @tparam P task parameters type @tparam F callable type @tparam I iterator type - @param name assigned name to the task + @param params task parameters @param func callable object @param first iterator to the beginning (inclusive) @param last iterator to the end (exclusive) @@ -1029,42 +1026,44 @@ class Executor { This member function is thread-safe. */ - template , AsyncTask>, void>* = nullptr + template && !std::is_same_v, AsyncTask>, void>* = nullptr > - auto dependent_async(const std::string& name, F&& func, I first, I last); + auto dependent_async(P&& params, F&& func, I first, I last); private: const size_t _MAX_STEALS; + + std::mutex _wsq_mutex; + std::mutex _taskflows_mutex; +#ifdef __cpp_lib_atomic_wait + std::atomic _num_topologies {0}; + std::atomic_flag _all_spawned = ATOMIC_FLAG_INIT; +#else std::condition_variable _topology_cv; - std::mutex _taskflows_mutex; std::mutex _topology_mutex; - std::mutex _wsq_mutex; - std::mutex _asyncs_mutex; - size_t _num_topologies {0}; +#endif std::unordered_map _wids; std::vector _threads; std::vector _workers; std::list _taskflows; - std::unordered_set> _asyncs; - Notifier _notifier; TaskQueue _wsq; std::atomic _done {0}; - std::shared_ptr _worker_interface; std::unordered_set> _observers; Worker* _this_worker(); - + bool _wait_for_task(Worker&, Node*&); + bool _invoke_module_task_internal(Worker&, Node*); void _observer_prologue(Worker&, Node*); void _observer_epilogue(Worker&, Node*); @@ -1076,48 +1075,45 @@ class Executor { void _schedule(Worker&, const SmallVector&); void _schedule(const SmallVector&); void _set_up_topology(Worker*, Topology*); + void _set_up_graph(Graph&, Node*, Topology*, int, SmallVector&); void _tear_down_topology(Worker&, Topology*); void _tear_down_async(Node*); void _tear_down_dependent_async(Worker&, Node*); void _tear_down_invoke(Worker&, Node*); void _increment_topology(); void _decrement_topology(); - void _decrement_topology_and_notify(); void _invoke(Worker&, Node*); void _invoke_static_task(Worker&, Node*); - void _invoke_dynamic_task(Worker&, Node*); - void _consume_graph(Worker&, Node*, Graph&); - void _detach_dynamic_task(Worker&, Node*, Graph&); + void _invoke_subflow_task(Worker&, Node*); + void _detach_subflow_task(Worker&, Node*, Graph&); void _invoke_condition_task(Worker&, Node*, SmallVector&); void _invoke_multi_condition_task(Worker&, Node*, SmallVector&); void _invoke_module_task(Worker&, Node*); void _invoke_async_task(Worker&, Node*); void _invoke_dependent_async_task(Worker&, Node*); void _process_async_dependent(Node*, tf::AsyncTask&, size_t&); + void _process_exception(Worker&, Node*); void _schedule_async_task(Node*); + void _corun_graph(Worker&, Node*, Graph&); template void _corun_until(Worker&, P&&); - - template - auto _make_promised_async(std::promise&&, F&&); }; // Constructor -inline Executor::Executor(size_t N, std::shared_ptr wix) : +inline Executor::Executor(size_t N) : _MAX_STEALS {((N+1) << 1)}, _threads {N}, _workers {N}, - _notifier {N}, - _worker_interface {std::move(wix)} { + _notifier {N} { if(N == 0) { - TF_THROW("no cpu workers to execute taskflows"); + TF_THROW("executor must define at least one worker"); } _spawn(N); - // instantite the default observer if requested + // initialize the default observer if requested if(has_env(TF_ENABLE_PROFILER)) { TFProfManager::get()._manage(make_observer()); } @@ -1146,7 +1142,11 @@ inline size_t Executor::num_workers() const noexcept { // Function: num_topologies inline size_t Executor::num_topologies() const { +#ifdef __cpp_lib_atomic_wait + return _num_topologies.load(std::memory_order_relaxed); +#else return _num_topologies; +#endif } // Function: num_taskflows @@ -1169,9 +1169,12 @@ inline int Executor::this_worker_id() const { // Procedure: _spawn inline void Executor::_spawn(size_t N) { +#ifdef __cpp_lib_atomic_wait +#else std::mutex mutex; std::condition_variable cond; size_t n=0; +#endif for(size_t id=0; id void { - - // assign the thread - w._thread = &_threads[w._id]; + _threads[id] = std::thread([&, &w=_workers[id]] () { - // enables the mapping +#ifdef __cpp_lib_atomic_wait + // wait for the caller thread to initialize the ID mapping + _all_spawned.wait(false, std::memory_order_acquire); + w._thread = &_threads[w._id]; +#else + // update the ID mapping of this thread + w._thread = &_threads[w._id]; { std::scoped_lock lock(mutex); _wids[std::this_thread::get_id()] = w._id; @@ -1195,41 +1199,22 @@ inline void Executor::_spawn(size_t N) { cond.notify_one(); } } +#endif Node* t = nullptr; - // before entering the scheduler (work-stealing loop), - // call the user-specified prologue function - if(_worker_interface) { - _worker_interface->scheduler_prologue(w); - } - - // must use 1 as condition instead of !done because - // the previous worker may stop while the following workers - // are still preparing for entering the scheduling loop - std::exception_ptr ptr{nullptr}; - try { - while(1) { - - // execute the tasks. - _exploit_task(w, t); - - // wait for tasks - if(_wait_for_task(w, t) == false) { - break; - } + while(1) { + + // execute the tasks. + _exploit_task(w, t); + + // wait for tasks + if(_wait_for_task(w, t) == false) { + break; } - } - catch(...) { - ptr = std::current_exception(); - } - - // call the user-specified epilogue function - if(_worker_interface) { - _worker_interface->scheduler_epilogue(w, ptr); } - }, std::ref(_workers[id]), std::ref(mutex), std::ref(cond), std::ref(n)); + }); // POSIX-like system can use the following to affine threads to cores //cpu_set_t cpuset; @@ -1238,10 +1223,22 @@ inline void Executor::_spawn(size_t N) { //pthread_setaffinity_np( // _threads[id].native_handle(), sizeof(cpu_set_t), &cpuset //); - } +#ifdef __cpp_lib_atomic_wait + //_wids[_threads[id].get_id()] = id; + _wids.emplace(std::piecewise_construct, + std::forward_as_tuple(_threads[id].get_id()), std::forward_as_tuple(id) + ); +#endif + } + +#ifdef __cpp_lib_atomic_wait + _all_spawned.test_and_set(std::memory_order_release); + _all_spawned.notify_all(); +#else std::unique_lock lock(mutex); cond.wait(lock, [&](){ return n==N; }); +#endif } // Function: _corun_until @@ -1521,6 +1518,8 @@ inline void Executor::_invoke(Worker& worker, Node* node) { while(!(node->_state.load(std::memory_order_acquire) & Node::READY)); begin_invoke: + + SmallVector conds; // no need to do other things if the topology is cancelled if(node->_is_cancelled()) { @@ -1540,7 +1539,6 @@ inline void Executor::_invoke(Worker& worker, Node* node) { // condition task //int cond = -1; - SmallVector conds; // switch is faster than nested if-else due to jump table switch(node->_handle.index()) { @@ -1550,9 +1548,9 @@ inline void Executor::_invoke(Worker& worker, Node* node) { } break; - // dynamic task - case Node::DYNAMIC: { - _invoke_dynamic_task(worker, node); + // subflow task + case Node::SUBFLOW: { + _invoke_subflow_task(worker, node); } break; @@ -1599,6 +1597,8 @@ inline void Executor::_invoke(Worker& worker, Node* node) { break; } + //invoke_successors: + // if releasing semaphores exist, release them if(node->_semaphores && !node->_semaphores->to_release.empty()) { _schedule(worker, node->_release_all()); @@ -1687,19 +1687,31 @@ inline void Executor::_invoke(Worker& worker, Node* node) { } } -// Proecdure: _tear_down_invoke +// Procedure: _tear_down_invoke inline void Executor::_tear_down_invoke(Worker& worker, Node* node) { - // we must check parent first before substracting the join counter, + // we must check parent first before subtracting the join counter, // or it can introduce data race - if(node->_parent == nullptr) { + if(auto parent = node->_parent; parent == nullptr) { if(node->_topology->_join_counter.fetch_sub(1, std::memory_order_acq_rel) == 1) { _tear_down_topology(worker, node->_topology); } } - // joined subflow - else { - node->_parent->_join_counter.fetch_sub(1, std::memory_order_release); + // Here we asssume the parent is in a busy loop (e.g., corun) waiting for + // its join counter to become 0. + else { + //parent->_join_counter.fetch_sub(1, std::memory_order_acq_rel); + parent->_join_counter.fetch_sub(1, std::memory_order_release); } + //// module task + //else { + // auto id = parent->_handle.index(); + // if(parent->_join_counter.fetch_sub(1, std::memory_order_acq_rel) == 1) { + // if(id == Node::MODULE) { + // return parent; + // } + // } + //} + //return nullptr; } // Procedure: _observer_prologue @@ -1716,47 +1728,68 @@ inline void Executor::_observer_epilogue(Worker& worker, Node* node) { } } +// Procedure: _process_exception +inline void Executor::_process_exception(Worker&, Node* node) { + + constexpr static auto flag = Topology::EXCEPTION | Topology::CANCELLED; + + // if the node has a parent, we store the exception in its parent + if(auto parent = node->_parent; parent) { + if ((parent->_state.fetch_or(Node::EXCEPTION, std::memory_order_relaxed) & Node::EXCEPTION) == 0) { + parent->_exception_ptr = std::current_exception(); + } + // TODO if the node has a topology, cancel it to enable early stop + //if(auto tpg = node->_topology; tpg) { + // tpg->_state.fetch_or(Topology::CANCELLED, std::memory_order_relaxed); + //} + } + // multiple tasks may throw, so we only take the first thrown exception + else if(auto tpg = node->_topology; tpg && + ((tpg->_state.fetch_or(flag, std::memory_order_relaxed) & Topology::EXCEPTION) == 0) + ) { + tpg->_exception_ptr = std::current_exception(); + } + // TODO: skip the exception that is not associated with any taskflows +} + // Procedure: _invoke_static_task inline void Executor::_invoke_static_task(Worker& worker, Node* node) { _observer_prologue(worker, node); - auto& work = std::get_if(&node->_handle)->work; - switch(work.index()) { - case 0: - std::get_if<0>(&work)->operator()(); - break; + TF_EXECUTOR_EXCEPTION_HANDLER(worker, node, { + auto& work = std::get_if(&node->_handle)->work; + switch(work.index()) { + case 0: + std::get_if<0>(&work)->operator()(); + break; - case 1: - Runtime rt(*this, worker, node); - std::get_if<1>(&work)->operator()(rt); - break; - } + case 1: + Runtime rt(*this, worker, node); + std::get_if<1>(&work)->operator()(rt); + node->_process_exception(); + break; + } + }); _observer_epilogue(worker, node); } -// Procedure: _invoke_dynamic_task -inline void Executor::_invoke_dynamic_task(Worker& w, Node* node) { - +// Procedure: _invoke_subflow_task +inline void Executor::_invoke_subflow_task(Worker& w, Node* node) { _observer_prologue(w, node); - - auto handle = std::get_if(&node->_handle); - - handle->subgraph._clear(); - - Subflow sf(*this, w, node, handle->subgraph); - - handle->work(sf); - - if(sf._joinable) { - _consume_graph(w, node, handle->subgraph); - } - + TF_EXECUTOR_EXCEPTION_HANDLER(w, node, { + auto handle = std::get_if(&node->_handle); + handle->subgraph._clear(); + Subflow sf(*this, w, node, handle->subgraph); + handle->work(sf); + if(sf._joinable) { + _corun_graph(w, node, handle->subgraph); + } + node->_process_exception(); + }); _observer_epilogue(w, node); } -// Procedure: _detach_dynamic_task -inline void Executor::_detach_dynamic_task( - Worker& w, Node* p, Graph& g -) { +// Procedure: _detach_subflow_task +inline void Executor::_detach_subflow_task(Worker& w, Node* p, Graph& g) { // graph is empty and has no async tasks if(g.empty() && p->_join_counter.load(std::memory_order_acquire) == 0) { @@ -1764,18 +1797,7 @@ inline void Executor::_detach_dynamic_task( } SmallVector src; - - for(auto n : g._nodes) { - - n->_state.store(Node::DETACHED, std::memory_order_relaxed); - n->_set_up_join_counter(); - n->_topology = p->_topology; - n->_parent = nullptr; - - if(n->num_dependents() == 0) { - src.push_back(n); - } - } + _set_up_graph(g, nullptr, p->_topology, Node::DETACHED, src); { std::lock_guard lock(p->_topology->_taskflow._mutex); @@ -1786,29 +1808,26 @@ inline void Executor::_detach_dynamic_task( _schedule(w, src); } -// Procedure: _consume_graph -inline void Executor::_consume_graph(Worker& w, Node* p, Graph& g) { +// Procedure: _corun_graph +inline void Executor::_corun_graph(Worker& w, Node* p, Graph& g) { - // graph is empty and has no async tasks + // assert(p); + + // graph is empty and has no async tasks (subflow) if(g.empty() && p->_join_counter.load(std::memory_order_acquire) == 0) { return; } SmallVector src; - for(auto n : g._nodes) { - n->_state.store(0, std::memory_order_relaxed); - n->_set_up_join_counter(); - n->_topology = p->_topology; - n->_parent = p; - if(n->num_dependents() == 0) { - src.push_back(n); - } - } + _set_up_graph(g, p, p->_topology, 0, src); p->_join_counter.fetch_add(src.size(), std::memory_order_relaxed); _schedule(w, src); - _corun_until(w, [p] () -> bool { return p->_join_counter.load(std::memory_order_acquire) == 0; }); + + _corun_until(w, [p] () -> bool { + return p->_join_counter.load(std::memory_order_acquire) == 0; } + ); } // Procedure: _invoke_condition_task @@ -1816,17 +1835,20 @@ inline void Executor::_invoke_condition_task( Worker& worker, Node* node, SmallVector& conds ) { _observer_prologue(worker, node); - auto& work = std::get_if(&node->_handle)->work; - switch(work.index()) { - case 0: - conds = { std::get_if<0>(&work)->operator()() }; - break; + TF_EXECUTOR_EXCEPTION_HANDLER(worker, node, { + auto& work = std::get_if(&node->_handle)->work; + switch(work.index()) { + case 0: + conds = { std::get_if<0>(&work)->operator()() }; + break; - case 1: - Runtime rt(*this, worker, node); - conds = { std::get_if<1>(&work)->operator()(rt) }; - break; - } + case 1: + Runtime rt(*this, worker, node); + conds = { std::get_if<1>(&work)->operator()(rt) }; + node->_process_exception(); + break; + } + }); _observer_epilogue(worker, node); } @@ -1835,41 +1857,88 @@ inline void Executor::_invoke_multi_condition_task( Worker& worker, Node* node, SmallVector& conds ) { _observer_prologue(worker, node); - auto& work = std::get_if(&node->_handle)->work; - switch(work.index()) { - case 0: - conds = std::get_if<0>(&work)->operator()(); - break; + TF_EXECUTOR_EXCEPTION_HANDLER(worker, node, { + auto& work = std::get_if(&node->_handle)->work; + switch(work.index()) { + case 0: + conds = std::get_if<0>(&work)->operator()(); + break; - case 1: - Runtime rt(*this, worker, node); - conds = std::get_if<1>(&work)->operator()(rt); - break; - } + case 1: + Runtime rt(*this, worker, node); + conds = std::get_if<1>(&work)->operator()(rt); + node->_process_exception(); + break; + } + }); _observer_epilogue(worker, node); } // Procedure: _invoke_module_task inline void Executor::_invoke_module_task(Worker& w, Node* node) { _observer_prologue(w, node); - _consume_graph( - w, node, std::get_if(&node->_handle)->graph - ); + TF_EXECUTOR_EXCEPTION_HANDLER(w, node, { + _corun_graph(w, node, std::get_if(&node->_handle)->graph); + node->_process_exception(); + }); _observer_epilogue(w, node); } +//// Function: _invoke_module_task_internal +//inline bool Executor::_invoke_module_task_internal(Worker& w, Node* p) { +// +// // acquire the underlying graph +// auto& g = std::get_if(&p->_handle)->graph; +// +// // no need to do anything if the graph is empty +// if(g.empty()) { +// return false; +// } +// +// SmallVector src; +// _set_up_graph(g, p, p->_topology, 0, src); +// p->_join_counter.fetch_add(src.size(), std::memory_order_relaxed); +// +// _schedule(w, src); +// return true; +//} + // Procedure: _invoke_async_task -inline void Executor::_invoke_async_task(Worker& w, Node* node) { - _observer_prologue(w, node); - std::get_if(&node->_handle)->work(); - _observer_epilogue(w, node); +inline void Executor::_invoke_async_task(Worker& worker, Node* node) { + _observer_prologue(worker, node); + TF_EXECUTOR_EXCEPTION_HANDLER(worker, node, { + auto& work = std::get_if(&node->_handle)->work; + switch(work.index()) { + case 0: + std::get_if<0>(&work)->operator()(); + break; + + case 1: + Runtime rt(*this, worker, node); + std::get_if<1>(&work)->operator()(rt); + break; + } + }); + _observer_epilogue(worker, node); } // Procedure: _invoke_dependent_async_task -inline void Executor::_invoke_dependent_async_task(Worker& w, Node* node) { - _observer_prologue(w, node); - std::get_if(&node->_handle)->work(); - _observer_epilogue(w, node); +inline void Executor::_invoke_dependent_async_task(Worker& worker, Node* node) { + _observer_prologue(worker, node); + TF_EXECUTOR_EXCEPTION_HANDLER(worker, node, { + auto& work = std::get_if(&node->_handle)->work; + switch(work.index()) { + case 0: + std::get_if<0>(&work)->operator()(); + break; + + case 1: + Runtime rt(*this, worker, node); + std::get_if<1>(&work)->operator()(rt); + break; + } + }); + _observer_epilogue(worker, node); } // Function: run @@ -1938,7 +2007,7 @@ tf::Future Executor::run_until(Taskflow& f, P&& p, C&& c) { _increment_topology(); - // Need to check the empty under the lock since dynamic task may + // Need to check the empty under the lock since subflow task may // define detached blocks that modify the taskflow at the same time bool empty; { @@ -1951,8 +2020,8 @@ tf::Future Executor::run_until(Taskflow& f, P&& p, C&& c) { c(); std::promise promise; promise.set_value(); - _decrement_topology_and_notify(); - return tf::Future(promise.get_future(), std::monostate{}); + _decrement_topology(); + return tf::Future(promise.get_future()); } // create a topology for this run @@ -1998,8 +2067,9 @@ void Executor::corun(T& target) { TF_THROW("corun must be called by a worker of the executor"); } - Node parent; // dummy parent - _consume_graph(*w, &parent, target.graph()); + Node parent; // auxiliary parent + _corun_graph(*w, &parent, target.graph()); + parent._process_exception(); } // Function: corun_until @@ -2013,32 +2083,46 @@ void Executor::corun_until(P&& predicate) { } _corun_until(*w, std::forward

(predicate)); + + // TODO: exception? } // Procedure: _increment_topology inline void Executor::_increment_topology() { +#ifdef __cpp_lib_atomic_wait + _num_topologies.fetch_add(1, std::memory_order_relaxed); +#else std::lock_guard lock(_topology_mutex); ++_num_topologies; +#endif } -// Procedure: _decrement_topology_and_notify -inline void Executor::_decrement_topology_and_notify() { +// Procedure: _decrement_topology +inline void Executor::_decrement_topology() { +#ifdef __cpp_lib_atomic_wait + if(_num_topologies.fetch_sub(1, std::memory_order_acq_rel) == 1) { + _num_topologies.notify_all(); + } +#else std::lock_guard lock(_topology_mutex); if(--_num_topologies == 0) { _topology_cv.notify_all(); } -} - -// Procedure: _decrement_topology -inline void Executor::_decrement_topology() { - std::lock_guard lock(_topology_mutex); - --_num_topologies; +#endif } // Procedure: wait_for_all inline void Executor::wait_for_all() { +#ifdef __cpp_lib_atomic_wait + size_t n = _num_topologies.load(std::memory_order_acquire); + while(n != 0) { + _num_topologies.wait(n, std::memory_order_acquire); + n = _num_topologies.load(std::memory_order_acquire); + } +#else std::unique_lock lock(_topology_mutex); _topology_cv.wait(lock, [&](){ return _num_topologies == 0; }); +#endif } // Function: _set_up_topology @@ -2048,21 +2132,7 @@ inline void Executor::_set_up_topology(Worker* worker, Topology* tpg) { tpg->_sources.clear(); tpg->_taskflow._graph._clear_detached(); - - // scan each node in the graph and build up the links - for(auto node : tpg->_taskflow._graph._nodes) { - - node->_topology = tpg; - node->_parent = nullptr; - node->_state.store(0, std::memory_order_relaxed); - - if(node->num_dependents() == 0) { - tpg->_sources.push_back(node); - } - - node->_set_up_join_counter(); - } - + _set_up_graph(tpg->_taskflow._graph, nullptr, tpg, 0, tpg->_sources); tpg->_join_counter.store(tpg->_sources.size(), std::memory_order_relaxed); if(worker) { @@ -2073,6 +2143,22 @@ inline void Executor::_set_up_topology(Worker* worker, Topology* tpg) { } } +// Function: _set_up_graph +inline void Executor::_set_up_graph( + Graph& g, Node* parent, Topology* tpg, int state, SmallVector& src +) { + for(auto node : g._nodes) { + node->_topology = tpg; + node->_parent = parent; + node->_state.store(state, std::memory_order_relaxed); + if(node->num_dependents() == 0) { + src.push_back(node); + } + node->_set_up_join_counter(); + node->_exception_ptr = nullptr; + } +} + // Function: _tear_down_topology inline void Executor::_tear_down_topology(Worker& worker, Topology* tpg) { @@ -2081,7 +2167,7 @@ inline void Executor::_tear_down_topology(Worker& worker, Topology* tpg) { //assert(&tpg == &(f._topologies.front())); // case 1: we still need to run the topology again - if(!tpg->_is_cancelled && !tpg->_pred()) { + if(!tpg->_exception_ptr && !tpg->cancelled() && !tpg->_pred()) { //assert(tpg->_join_counter == 0); std::lock_guard lock(f._mutex); tpg->_join_counter.store(tpg->_sources.size(), std::memory_order_relaxed); @@ -2114,36 +2200,24 @@ inline void Executor::_tear_down_topology(Worker& worker, Topology* tpg) { else { //assert(f._topologies.size() == 1); - // Need to back up the promise first here becuz taskflow might be - // destroy soon after calling get - auto p {std::move(tpg->_promise)}; - - // Back up lambda capture in case it has the topology pointer, - // to avoid it releasing on pop_front ahead of _mutex.unlock & - // _promise.set_value. Released safely when leaving scope. - auto c {std::move(tpg->_call)}; - - // Get the satellite if any - auto s {f._satellite}; - - // Now we remove the topology from this taskflow + auto fetched_tpg {std::move(f._topologies.front())}; f._topologies.pop(); + auto satellite {f._satellite}; - //f._mutex.unlock(); lock.unlock(); + + // Soon after we carry out the promise, there is no longer any guarantee + // for the lifetime of the associated taskflow. + fetched_tpg->_carry_out_promise(); - // We set the promise in the end in case taskflow leaves the scope. - // After set_value, the caller will return from wait - p.set_value(); - - _decrement_topology_and_notify(); + _decrement_topology(); // remove the taskflow if it is managed by the executor // TODO: in the future, we may need to synchronize on wait // (which means the following code should the moved before set_value) - if(s) { - std::scoped_lock lock(_taskflows_mutex); - _taskflows.erase(*s); + if(satellite) { + std::scoped_lock satellite_lock(_taskflows_mutex); + _taskflows.erase(*satellite); } } } @@ -2162,7 +2236,11 @@ inline void Subflow::join() { } // only the parent worker can join the subflow - _executor._consume_graph(_worker, _parent, _graph); + _executor._corun_graph(_worker, _parent, _graph); + + // if any exception is caught from subflow tasks, rethrow it + _parent->_process_exception(); + _joinable = false; } @@ -2175,7 +2253,7 @@ inline void Subflow::detach() { } // only the parent worker can detach the subflow - _executor._detach_dynamic_task(_worker, _parent, _graph); + _executor._detach_subflow_task(_worker, _parent, _graph); _joinable = false; } @@ -2201,36 +2279,44 @@ inline void Runtime::schedule(Task task) { // Procedure: corun template void Runtime::corun(T&& target) { - - // dynamic task (subflow) - if constexpr(is_dynamic_task_v) { - Graph graph; - Subflow sf(_executor, _worker, _parent, graph); - target(sf); - if(sf._joinable) { - _executor._consume_graph(_worker, _parent, graph); - } - } - // a composable graph object with `tf::Graph& T::graph()` defined - else { - _executor._consume_graph(_worker, _parent, target.graph()); - } + _executor._corun_graph(_worker, _parent, target.graph()); + _parent->_process_exception(); } // Procedure: corun_until template void Runtime::corun_until(P&& predicate) { _executor._corun_until(_worker, std::forward

(predicate)); + // TODO: exception? +} + +// Function: corun_all +inline void Runtime::corun_all() { + _executor._corun_until(_worker, [this] () -> bool { + return _parent->_join_counter.load(std::memory_order_acquire) == 0; + }); + _parent->_process_exception(); } +// Destructor +inline Runtime::~Runtime() { + _executor._corun_until(_worker, [this] () -> bool { + return _parent->_join_counter.load(std::memory_order_acquire) == 0; + }); +} + +// ------------------------------------ +// Runtime::silent_async series +// ------------------------------------ + // Function: _silent_async -template -void Runtime::_silent_async(Worker& w, const std::string& name, F&& f) { +template +void Runtime::_silent_async(Worker& w, P&& params, F&& f) { _parent->_join_counter.fetch_add(1, std::memory_order_relaxed); auto node = node_pool.animate( - name, 0, _parent->_topology, _parent, 0, + std::forward

(params), _parent->_topology, _parent, 0, std::in_place_type_t{}, std::forward(f) ); @@ -2240,44 +2326,46 @@ void Runtime::_silent_async(Worker& w, const std::string& name, F&& f) { // Function: silent_async template void Runtime::silent_async(F&& f) { - _silent_async(*_executor._this_worker(), "", std::forward(f)); + _silent_async(*_executor._this_worker(), DefaultTaskParams{}, std::forward(f)); } // Function: silent_async -template -void Runtime::silent_async(const std::string& name, F&& f) { - _silent_async(*_executor._this_worker(), name, std::forward(f)); +template +void Runtime::silent_async(P&& params, F&& f) { + _silent_async(*_executor._this_worker(), std::forward

(params), std::forward(f)); } // Function: silent_async_unchecked template -void Runtime::silent_async_unchecked(const std::string& name, F&& f) { - _silent_async(_worker, name, std::forward(f)); +void Runtime::silent_async_unchecked(F&& f) { + _silent_async(_worker, DefaultTaskParams{}, std::forward(f)); } +// Function: silent_async_unchecked +template +void Runtime::silent_async_unchecked(P&& params, F&& f) { + _silent_async(_worker, std::forward

(params), std::forward(f)); +} + +// ------------------------------------ +// Runtime::async series +// ------------------------------------ + // Function: _async -template -auto Runtime::_async(Worker& w, const std::string& name, F&& f) { +template +auto Runtime::_async(Worker& w, P&& params, F&& f) { _parent->_join_counter.fetch_add(1, std::memory_order_relaxed); using R = std::invoke_result_t>; - std::promise p; + std::packaged_task p(std::forward(f)); auto fu{p.get_future()}; auto node = node_pool.animate( - name, 0, _parent->_topology, _parent, 0, + std::forward

(params), _parent->_topology, _parent, 0, std::in_place_type_t{}, - [p=make_moc(std::move(p)), f=std::forward(f)] () mutable { - if constexpr(std::is_same_v) { - f(); - p.object.set_value(); - } - else { - p.object.set_value(f()); - } - } + [p=make_moc(std::move(p))] () mutable { p.object(); } ); _executor._schedule(w, node); @@ -2288,21 +2376,16 @@ auto Runtime::_async(Worker& w, const std::string& name, F&& f) { // Function: async template auto Runtime::async(F&& f) { - return _async(*_executor._this_worker(), "", std::forward(f)); + return _async(*_executor._this_worker(), DefaultTaskParams{}, std::forward(f)); } // Function: async -template -auto Runtime::async(const std::string& name, F&& f) { - return _async(*_executor._this_worker(), name, std::forward(f)); +template +auto Runtime::async(P&& params, F&& f) { + return _async(*_executor._this_worker(), std::forward

(params), std::forward(f)); } -// Function: join -inline void Runtime::join() { - corun_until([this] () -> bool { - return _parent->_join_counter.load(std::memory_order_acquire) == 0; - }); -} + } // end of namespace tf ----------------------------------------------------- diff --git a/bundled/taskflow-3.6.0/include/taskflow/core/flow_builder.hpp b/bundled/taskflow-3.7.0/taskflow/core/flow_builder.hpp similarity index 87% rename from bundled/taskflow-3.6.0/include/taskflow/core/flow_builder.hpp rename to bundled/taskflow-3.7.0/taskflow/core/flow_builder.hpp index 3e90d8e74d..df1d02fc52 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/core/flow_builder.hpp +++ b/bundled/taskflow-3.7.0/taskflow/core/flow_builder.hpp @@ -71,10 +71,10 @@ class FlowBuilder { }); @endcode - Please refer to @ref DynamicTasking for details. + Please refer to @ref SubflowTasking for details. */ template , void>* = nullptr + std::enable_if_t, void>* = nullptr > Task emplace(C&& callable); @@ -221,7 +221,7 @@ class FlowBuilder { The taskflow object @c t2 is composed of another taskflow object @c t1, preceded by another static task @c init. When taskflow @c t2 is submitted to an executor, - @c init will run first and then @c comp which spwans its definition + @c init will run first and then @c comp which spawns its definition in taskflow @c t1. The target @c object being composed must define the method @@ -313,6 +313,7 @@ class FlowBuilder { */ void linearize(std::initializer_list tasks); + // ------------------------------------------------------------------------ // parallel iterations // ------------------------------------------------------------------------ @@ -323,7 +324,7 @@ class FlowBuilder { @tparam B beginning iterator type @tparam E ending iterator type @tparam C callable type - @tparam P partitioner type (default tf::GuidedPartitioner) + @tparam P partitioner type (default tf::DefaultPartitioner) @param first iterator to the beginning (inclusive) @param last iterator to the end (exclusive) @@ -348,8 +349,8 @@ class FlowBuilder { Please refer to @ref ParallelIterations for details. */ - template - Task for_each(B first, E last, C callable, P&& part = P()); + template + Task for_each(B first, E last, C callable, P part = P()); /** @brief constructs an STL-styled index-based parallel-for task @@ -358,7 +359,7 @@ class FlowBuilder { @tparam E ending index type (must be integral) @tparam S step type (must be integral) @tparam C callable type - @tparam P partitioner type (default tf::GuidedPartitioner) + @tparam P partitioner type (default tf::DefaultPartitioner) @param first index of the beginning (inclusive) @param last index of the end (exclusive) @@ -389,9 +390,9 @@ class FlowBuilder { Please refer to @ref ParallelIterations for details. */ - template + template Task for_each_index( - B first, E last, S step, C callable, P&& part = P() + B first, E last, S step, C callable, P part = P() ); // ------------------------------------------------------------------------ @@ -405,7 +406,7 @@ class FlowBuilder { @tparam E ending input iterator type @tparam O output iterator type @tparam C callable type - @tparam P partitioner type (default tf::GuidedPartitioner) + @tparam P partitioner type (default tf::DefaultPartitioner) @param first1 iterator to the beginning of the first range @param last1 iterator to the end of the first range @@ -432,9 +433,10 @@ class FlowBuilder { Please refer to @ref ParallelTransforms for details. */ template < - typename B, typename E, typename O, typename C, typename P = GuidedPartitioner + typename B, typename E, typename O, typename C, typename P = DefaultPartitioner, + std::enable_if_t>, void>* = nullptr > - Task transform(B first1, E last1, O d_first, C c, P&& part = P()); + Task transform(B first1, E last1, O d_first, C c, P part = P()); /** @brief constructs a parallel-transform task @@ -444,7 +446,7 @@ class FlowBuilder { @tparam B2 beginning input iterator type for the first second range @tparam O output iterator type @tparam C callable type - @tparam P partitioner type (default tf::GuidedPartitioner) + @tparam P partitioner type (default tf::DefaultPartitioner) @param first1 iterator to the beginning of the first input range @param last1 iterator to the end of the first input range @@ -472,10 +474,10 @@ class FlowBuilder { Please refer to @ref ParallelTransforms for details. */ template < - typename B1, typename E1, typename B2, typename O, typename C, typename P=GuidedPartitioner, + typename B1, typename E1, typename B2, typename O, typename C, typename P=DefaultPartitioner, std::enable_if_t>, void>* = nullptr > - Task transform(B1 first1, E1 last1, B2 first2, O d_first, C c, P&& part = P()); + Task transform(B1 first1, E1 last1, B2 first2, O d_first, C c, P part = P()); // ------------------------------------------------------------------------ // reduction @@ -488,7 +490,7 @@ class FlowBuilder { @tparam E ending iterator type @tparam T result type @tparam O binary reducer type - @tparam P partitioner type (default tf::GuidedPartitioner) + @tparam P partitioner type (default tf::DefaultPartitioner) @param first iterator to the beginning (inclusive) @param last iterator to the end (exclusive) @@ -513,11 +515,11 @@ class FlowBuilder { Please refer to @ref ParallelReduction for details. */ - template - Task reduce(B first, E last, T& init, O bop, P&& part = P()); + template + Task reduce(B first, E last, T& init, O bop, P part = P()); // ------------------------------------------------------------------------ - // transfrom and reduction + // transform and reduction // ------------------------------------------------------------------------ /** @@ -527,8 +529,8 @@ class FlowBuilder { @tparam E ending iterator type @tparam T result type @tparam BOP binary reducer type - @tparam UOP unary transformion type - @tparam P partitioner type (default tf::GuidedPartitioner) + @tparam UOP unary transformation type + @tparam P partitioner type (default tf::DefaultPartitioner) @param first iterator to the beginning (inclusive) @param last iterator to the end (exclusive) @@ -555,10 +557,56 @@ class FlowBuilder { Please refer to @ref ParallelReduction for details. */ template < - typename B, typename E, typename T, typename BOP, typename UOP, typename P = GuidedPartitioner + typename B, typename E, typename T, typename BOP, typename UOP, typename P = DefaultPartitioner, + std::enable_if_t>, void>* = nullptr > - Task transform_reduce(B first, E last, T& init, BOP bop, UOP uop, P&& part = P()); + Task transform_reduce(B first, E last, T& init, BOP bop, UOP uop, P part = P()); + + /** + @brief constructs an STL-styled parallel transform-reduce task + @tparam B1 first beginning iterator type + @tparam E1 first ending iterator type + @tparam B2 second beginning iterator type + @tparam T result type + @tparam BOP_R binary reducer type + @tparam BOP_T binary transformation type + @tparam P partitioner type (default tf::DefaultPartitioner) + + @param first1 iterator to the beginning of the first range (inclusive) + @param last1 iterator to the end of the first range (exclusive) + @param first2 iterator to the beginning of the second range + @param init initial value of the reduction and the storage for the reduced result + @param bop_r binary operator that will be applied in unspecified order to the results of @c bop_t + @param bop_t binary operator that will be applied to transform each element in the range to the result type + @param part partitioning algorithm to schedule parallel iterations + + @return a tf::Task handle + + The task spawns asynchronous tasks to perform parallel reduction over @c init and + transformed elements in the range [first, last). + The reduced result is store in @c init. + This method is equivalent to the parallel execution of the following loop: + + @code{.cpp} + for(auto itr1=first1, itr2=first2; itr1!=last1; itr1++, itr2++) { + init = bop_r(init, bop_t(*itr1, *itr2)); + } + @endcode + + Iterators are templated to enable stateful range using std::reference_wrapper. + + Please refer to @ref ParallelReduction for details. + */ + template < + typename B1, typename E1, typename B2, typename T, typename BOP_R, typename BOP_T, + typename P = DefaultPartitioner, + std::enable_if_t>, void>* = nullptr + > + Task transform_reduce( + B1 first1, E1 last1, B2 first2, T& init, BOP_R bop_r, BOP_T bop_t, P part = P() + ); + // ------------------------------------------------------------------------ // scan // ------------------------------------------------------------------------ @@ -570,11 +618,13 @@ class FlowBuilder { @tparam E ending iterator type @tparam D destination iterator type @tparam BOP summation operator type + @tparam P partitioner type (default tf::DefaultPartitioner) @param first start of input range @param last end of input range @param d_first start of output range (may be the same as input range) @param bop function to perform summation + @param part partitioning algorithm to schedule parallel iterations Performs the cumulative sum (aka prefix sum, aka scan) of the input range and writes the result to the output range. @@ -600,8 +650,10 @@ class FlowBuilder { Please refer to @ref ParallelScan for details. */ - template - Task inclusive_scan(B first, E last, D d_first, BOP bop); + template >, void>* = nullptr + > + Task inclusive_scan(B first, E last, D d_first, BOP bop, P part = P()); /** @brief creates an STL-styled parallel inclusive-scan task with an initial value @@ -611,12 +663,14 @@ class FlowBuilder { @tparam D destination iterator type @tparam BOP summation operator type @tparam T initial value type + @tparam P partitioner type (default tf::DefaultPartitioner) @param first start of input range @param last end of input range @param d_first start of output range (may be the same as input range) @param bop function to perform summation @param init initial value + @param part partitioning algorithm to schedule parallel iterations Performs the cumulative sum (aka prefix sum, aka scan) of the input range and writes the result to the output range. @@ -643,8 +697,10 @@ class FlowBuilder { Please refer to @ref ParallelScan for details. */ - template - Task inclusive_scan(B first, E last, D d_first, BOP bop, T init); + template >, void>* = nullptr + > + Task inclusive_scan(B first, E last, D d_first, BOP bop, T init, P part = P()); /** @brief creates an STL-styled parallel exclusive-scan task @@ -654,12 +710,14 @@ class FlowBuilder { @tparam D destination iterator type @tparam T initial value type @tparam BOP summation operator type + @tparam P partitioner type (default tf::DefaultPartitioner) @param first start of input range @param last end of input range @param d_first start of output range (may be the same as input range) @param init initial value @param bop function to perform summation + @param part partitioning algorithm to schedule parallel iterations Performs the cumulative sum (aka prefix sum, aka scan) of the input range and writes the result to the output range. @@ -685,8 +743,8 @@ class FlowBuilder { Please refer to @ref ParallelScan for details. */ - template - Task exclusive_scan(B first, E last, D d_first, T init, BOP bop); + template + Task exclusive_scan(B first, E last, D d_first, T init, BOP bop, P part = P()); // ------------------------------------------------------------------------ // transform scan @@ -700,12 +758,14 @@ class FlowBuilder { @tparam D destination iterator type @tparam BOP summation operator type @tparam UOP transform operator type + @tparam P partitioner type (default tf::DefaultPartitioner) @param first start of input range @param last end of input range @param d_first start of output range (may be the same as input range) @param bop function to perform summation @param uop function to transform elements of the input range + @param part partitioning algorithm to schedule parallel iterations Write the cumulative sum (aka prefix sum, aka scan) of the input range to the output range. Each element of the output range contains the @@ -732,8 +792,10 @@ class FlowBuilder { Please refer to @ref ParallelScan for details. */ - template - Task transform_inclusive_scan(B first, E last, D d_first, BOP bop, UOP uop); + template >, void>* = nullptr + > + Task transform_inclusive_scan(B first, E last, D d_first, BOP bop, UOP uop, P part = P()); /** @brief creates an STL-styled parallel transform-inclusive scan task @@ -744,6 +806,7 @@ class FlowBuilder { @tparam BOP summation operator type @tparam UOP transform operator type @tparam T initial value type + @tparam P partitioner type (default tf::DefaultPartitioner) @param first start of input range @param last end of input range @@ -751,6 +814,7 @@ class FlowBuilder { @param bop function to perform summation @param uop function to transform elements of the input range @param init initial value + @param part partitioning algorithm to schedule parallel iterations Write the cumulative sum (aka prefix sum, aka scan) of the input range to the output range. Each element of the output range contains the @@ -778,8 +842,10 @@ class FlowBuilder { Please refer to @ref ParallelScan for details. */ - template - Task transform_inclusive_scan(B first, E last, D d_first, BOP bop, UOP uop, T init); + template >, void>* = nullptr + > + Task transform_inclusive_scan(B first, E last, D d_first, BOP bop, UOP uop, T init, P part = P()); /** @brief creates an STL-styled parallel transform-exclusive scan task @@ -790,6 +856,7 @@ class FlowBuilder { @tparam BOP summation operator type @tparam UOP transform operator type @tparam T initial value type + @tparam P partitioner type (default tf::DefaultPartitioner) @param first start of input range @param last end of input range @@ -797,6 +864,7 @@ class FlowBuilder { @param bop function to perform summation @param uop function to transform elements of the input range @param init initial value + @param part partitioning algorithm to schedule parallel iterations Write the cumulative sum (aka prefix sum, aka scan) of the input range to the output range. Each element of the output range contains the @@ -823,8 +891,8 @@ class FlowBuilder { Please refer to @ref ParallelScan for details. */ - template - Task transform_exclusive_scan(B first, E last, D d_first, T init, BOP bop, UOP uop); + template + Task transform_exclusive_scan(B first, E last, D d_first, T init, BOP bop, UOP uop, P part = P()); // ------------------------------------------------------------------------ // find @@ -843,7 +911,7 @@ class FlowBuilder { @param last end of the input range @param result resulting iterator to the found element in the input range @param predicate unary predicate which returns @c true for the required element - @param part partitioning algorithm (default tf::GuidedPartitioner) + @param part partitioning algorithm (default tf::DefaultPartitioner) Returns an iterator to the first element in the range [first, last) that satisfies the given criteria (or last if there is no such iterator). @@ -875,9 +943,9 @@ class FlowBuilder { Iterators are templated to enable stateful range using std::reference_wrapper. */ - template - Task find_if(B first, E last, T& result, UOP predicate, P&& part = P()); - + template + Task find_if(B first, E last, T &result, UOP predicate, P part = P()); + /** @brief constructs a task to perform STL-styled find-if-not algorithm @@ -891,7 +959,7 @@ class FlowBuilder { @param last end of the input range @param result resulting iterator to the found element in the input range @param predicate unary predicate which returns @c false for the required element - @param part partitioning algorithm (default tf::GuidedPartitioner) + @param part partitioning algorithm (default tf::DefaultPartitioner) Returns an iterator to the first element in the range [first, last) that satisfies the given criteria (or last if there is no such iterator). @@ -923,8 +991,8 @@ class FlowBuilder { Iterators are templated to enable stateful range using std::reference_wrapper. */ - template - Task find_if_not(B first, E last, T& result, UOP predicate, P&& part = P()); + template + Task find_if_not(B first, E last, T &result, UOP predicate, P part = P()); /** @brief constructs a task to perform STL-styled min-element algorithm @@ -939,7 +1007,7 @@ class FlowBuilder { @param last end of the input range @param result resulting iterator to the found element in the input range @param comp comparison function object - @param part partitioning algorithm (default tf::GuidedPartitioner) + @param part partitioning algorithm (default tf::DefaultPartitioner) Finds the smallest element in the [first, last) using the given comparison function object. @@ -976,7 +1044,7 @@ class FlowBuilder { Iterators are templated to enable stateful range using std::reference_wrapper. */ template - Task min_element(B first, E last, T& result, C comp, P&& part); + Task min_element(B first, E last, T& result, C comp, P part); /** @brief constructs a task to perform STL-styled max-element algorithm @@ -991,7 +1059,7 @@ class FlowBuilder { @param last end of the input range @param result resulting iterator to the found element in the input range @param comp comparison function object - @param part partitioning algorithm (default tf::GuidedPartitioner) + @param part partitioning algorithm (default tf::DefaultPartitioner) Finds the largest element in the [first, last) using the given comparison function object. @@ -1028,7 +1096,7 @@ class FlowBuilder { Iterators are templated to enable stateful range using std::reference_wrapper. */ template - Task max_element(B first, E last, T& result, C comp, P&& part); + Task max_element(B first, E last, T& result, C comp, P part); // ------------------------------------------------------------------------ // sort @@ -1065,7 +1133,7 @@ class FlowBuilder { @param first iterator to the beginning (inclusive) @param last iterator to the end (exclusive) - The task spawns asynchronous tasks to parallelly sort elements in the range + The task spawns asynchronous tasks to parallel sort elements in the range [first, last) using the @c std::less comparator, where @c T is the dereferenced iterator type. @@ -1103,10 +1171,10 @@ Task FlowBuilder::emplace(C&& c) { } // Function: emplace -template , void>*> +template , void>*> Task FlowBuilder::emplace(C&& c) { return Task(_graph._emplace_back("", 0, nullptr, nullptr, 0, - std::in_place_type_t{}, std::forward(c) + std::in_place_type_t{}, std::forward(c) )); } diff --git a/bundled/taskflow-3.6.0/include/taskflow/core/graph.hpp b/bundled/taskflow-3.7.0/taskflow/core/graph.hpp similarity index 77% rename from bundled/taskflow-3.6.0/include/taskflow/core/graph.hpp rename to bundled/taskflow-3.7.0/taskflow/core/graph.hpp index 475422d082..efaa4ffc4c 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/core/graph.hpp +++ b/bundled/taskflow-3.7.0/taskflow/core/graph.hpp @@ -25,6 +25,7 @@ namespace tf { // Class: Graph // ---------------------------------------------------------------------------- + /** @class Graph @@ -147,6 +148,14 @@ class Runtime { friend class FlowBuilder; public: + + /** + @brief destroys the runtime object + + Issues a tf::Runtime::corun_all to finish all spawned asynchronous tasks + and then destroys the runtime object. + */ + ~Runtime(); /** @brief obtains the running executor @@ -211,9 +220,9 @@ class Runtime { The method creates an asynchronous task to launch the given function on the given arguments. The difference to tf::Executor::async is that the created asynchronous task - pertains to the runtime. - When the runtime joins, all asynchronous tasks created from the runtime - are guaranteed to finish after the join returns. + pertains to the runtime object. + Applications can explicitly issue tf::Runtime::corun_all + to wait for all spawned asynchronous tasks to finish. For example: @code{.cpp} @@ -230,8 +239,8 @@ class Runtime { rt.async([&](){ counter++; }); } - // explicit join 100 asynchronous tasks - rt.join(); + // wait for the 100 asynchronous tasks to finish + rt.corun_all(); assert(counter == 102); }); @endcode @@ -254,8 +263,8 @@ class Runtime { }); } - // explicit join 100 asynchronous tasks - rt.join(); + // wait for the 200 asynchronous tasks to finish + rt.corun_all(); assert(counter == 200); }); @endcode @@ -264,11 +273,12 @@ class Runtime { auto async(F&& f); /** - @brief similar to tf::Runtime::async but assigns the task a name + @brief runs the given callable asynchronously @tparam F callable type + @tparam P task parameters type - @param name assigned name to the task + @param params task parameters @param f callable @code{.cpp} @@ -279,8 +289,8 @@ class Runtime { @endcode */ - template - auto async(const std::string& name, F&& f); + template + auto async(P&& params, F&& f); /** @brief runs the given function asynchronously without returning any future object @@ -297,7 +307,7 @@ class Runtime { for(int i=0; i<100; i++) { rt.silent_async([&](){ counter++; }); } - rt.join(); + rt.corun_all(); assert(counter == 100); }); @endcode @@ -308,28 +318,50 @@ class Runtime { void silent_async(F&& f); /** - @brief similar to tf::Runtime::silent_async but assigns the task a name + @brief runs the given function asynchronously without returning any future object @tparam F callable type - @param name assigned name to the task + @param params task parameters @param f callable @code{.cpp} taskflow.emplace([&](tf::Runtime& rt){ rt.silent_async("my task", [](){}); - rt.join(); + rt.corun_all(); + }); + @endcode + */ + template + void silent_async(P&& params, F&& f); + + /** + @brief similar to tf::Runtime::silent_async but the caller must be the worker of the runtime + + @tparam F callable type + + @param f callable + + The method bypass the check of the caller worker from the executor + and thus can only called by the worker of this runtime. + + @code{.cpp} + taskflow.emplace([&](tf::Runtime& rt){ + // running by the worker of this runtime + rt.silent_async_unchecked([](){}); + rt.corun_all(); }); @endcode */ template - void silent_async(const std::string& name, F&& f); + void silent_async_unchecked(F&& f); /** @brief similar to tf::Runtime::silent_async but the caller must be the worker of the runtime @tparam F callable type + @tparam P task parameters type - @param name assigned name to the task + @param params task parameters @param f callable The method bypass the check of the caller worker from the executor @@ -339,18 +371,18 @@ class Runtime { taskflow.emplace([&](tf::Runtime& rt){ // running by the worker of this runtime rt.silent_async_unchecked("my task", [](){}); - rt.join(); + rt.corun_all(); }); @endcode */ - template - void silent_async_unchecked(const std::string& name, F&& f); + template + void silent_async_unchecked(P&& params, F&& f); /** @brief co-runs the given target and waits until it completes A target can be one of the following forms: - + a dynamic task to spawn a subflow or + + a subflow task to spawn a subflow or + a composable graph object with `tf::Graph& T::graph()` defined @code{.cpp} @@ -376,6 +408,9 @@ class Runtime { the caller thread (worker) is not blocked (e.g., sleeping or holding any lock). Instead, the caller thread joins the work-stealing loop of the executor and returns when all tasks in the target completes. + + @attention + Only the worker of this tf::Runtime can issue corun. */ template void corun(T&& target); @@ -388,41 +423,43 @@ class Runtime { The method keeps the caller worker running in the work-stealing loop until the stop predicate becomes true. + + @attention + Only the worker of this tf::Runtime can issue corun. */ template void corun_until(P&& predicate); /** - @brief joins all asynchronous tasks spawned by this runtime + @brief corun all asynchronous tasks spawned by this runtime with other workers - Immediately joins all asynchronous tasks (tf::Runtime::async, - tf::Runtime::silent_async). - Unlike tf::Subflow::join, you can join multiples times from - a tf::Runtime object. + Coruns all asynchronous tasks (tf::Runtime::async, + tf::Runtime::silent_async) with other workers until all those + asynchronous tasks finish. @code{.cpp} std::atomic counter{0}; taskflow.emplace([&](tf::Runtime& rt){ - // spawn 100 async tasks and join + // spawn 100 async tasks and wait for(int i=0; i<100; i++) { rt.silent_async([&](){ counter++; }); } - rt.join(); + rt.corun_all(); assert(counter == 100); - // spawn another 100 async tasks and join + // spawn another 100 async tasks and wait for(int i=0; i<100; i++) { rt.silent_async([&](){ counter++; }); } - rt.join(); + rt.corun_all(); assert(counter == 200); }); @endcode @attention - Only the worker of this tf::Runtime can issue join. + Only the worker of this tf::Runtime can issue tf::Runtime::corun_all. */ - inline void join(); + inline void corun_all(); /** @brief acquire a reference to the underlying worker @@ -454,14 +491,14 @@ class Runtime { /** @private */ - template - auto _async(Worker& w, const std::string& name, F&& f); + template + auto _async(Worker& w, P&& params, F&& f); /** @private */ - template - void _silent_async(Worker& w, const std::string& name, F&& f); + template + void _silent_async(Worker& w, P&& params, F&& f); }; // constructor @@ -481,6 +518,54 @@ inline Worker& Runtime::worker() { return _worker; } +// ---------------------------------------------------------------------------- +// TaskParams +// ---------------------------------------------------------------------------- + +/** +@struct TaskParams + +@brief task parameters to use when creating an asynchronous task +*/ +struct TaskParams { + /** + @brief name of the task + */ + std::string name; + + /** + @brief priority of the tassk + */ + unsigned priority {0}; + + /** + @brief C-styled pointer to user data + */ + void* data {nullptr}; +}; + +/** +@struct DefaultTaskParams + +@brief empty task parameter type for compile-time optimization +*/ +struct DefaultTaskParams { +}; + +/** +@brief determines if the given type is a task parameter type + +Task parameters can be specified in one of the following types: + + tf::TaskParams: assign the struct of defined parameters + + tf::DefaultTaskParams: assign nothing + + std::string: assign a name to the task +*/ +template +constexpr bool is_task_params_v = + std::is_same_v, TaskParams> || + std::is_same_v, DefaultTaskParams> || + std::is_constructible_v; + // ---------------------------------------------------------------------------- // Node // ---------------------------------------------------------------------------- @@ -492,6 +577,7 @@ class Node { friend class Graph; friend class Task; + friend class AsyncTask; friend class TaskView; friend class Taskflow; friend class Executor; @@ -512,6 +598,7 @@ class Node { constexpr static int DETACHED = 2; constexpr static int ACQUIRED = 4; constexpr static int READY = 8; + constexpr static int EXCEPTION = 16; using Placeholder = std::monostate; @@ -526,13 +613,13 @@ class Node { > work; }; - // dynamic work handle - struct Dynamic { + // subflow work handle + struct Subflow { template - Dynamic(C&&); + Subflow(C&&); - std::function work; + std::function work; Graph subgraph; }; @@ -573,7 +660,9 @@ class Node { template Async(T&&); - std::function work; + std::variant< + std::function, std::function + > work; }; // silent dependent async @@ -582,20 +671,23 @@ class Node { template DependentAsync(C&&); - std::function work; - + std::variant< + std::function, std::function + > work; + + std::atomic use_count {1}; std::atomic state {AsyncState::UNFINISHED}; }; using handle_t = std::variant< Placeholder, // placeholder Static, // static tasking - Dynamic, // dynamic tasking + Subflow, // subflow tasking Condition, // conditional tasking MultiCondition, // multi-conditional tasking Module, // composable tasking Async, // async tasking - DependentAsync // dependent async tasking (no future) + DependentAsync // dependent async tasking >; struct Semaphores { @@ -608,7 +700,7 @@ class Node { // variant index constexpr static auto PLACEHOLDER = get_index_v; constexpr static auto STATIC = get_index_v; - constexpr static auto DYNAMIC = get_index_v; + constexpr static auto SUBFLOW = get_index_v; constexpr static auto CONDITION = get_index_v; constexpr static auto MULTI_CONDITION = get_index_v; constexpr static auto MODULE = get_index_v; @@ -618,7 +710,16 @@ class Node { Node() = default; template - Node(const std::string&, unsigned, Topology*, Node*, size_t, Args&&... args); + Node(const std::string&, unsigned, Topology*, Node*, size_t, Args&&...); + + template + Node(const std::string&, Topology*, Node*, size_t, Args&&...); + + template + Node(const TaskParams&, Topology*, Node*, size_t, Args&&...); + + template + Node(const DefaultTaskParams&, Topology*, Node*, size_t, Args&&...); ~Node(); @@ -635,11 +736,11 @@ class Node { unsigned _priority {0}; + void* _data {nullptr}; + Topology* _topology {nullptr}; Node* _parent {nullptr}; - void* _data {nullptr}; - SmallVector _successors; SmallVector _dependents; @@ -647,11 +748,13 @@ class Node { std::atomic _join_counter {0}; std::unique_ptr _semaphores; + std::exception_ptr _exception_ptr {nullptr}; handle_t _handle; void _precede(Node*); void _set_up_join_counter(); + void _process_exception(); bool _is_cancelled() const; bool _is_conditioner() const; @@ -679,12 +782,12 @@ Node::Static::Static(C&& c) : work {std::forward(c)} { } // ---------------------------------------------------------------------------- -// Definition for Node::Dynamic +// Definition for Node::Subflow // ---------------------------------------------------------------------------- // Constructor template -Node::Dynamic::Dynamic(C&& c) : work {std::forward(c)} { +Node::Subflow::Subflow(C&& c) : work {std::forward(c)} { } // ---------------------------------------------------------------------------- @@ -754,19 +857,65 @@ Node::Node( _handle {std::forward(args)...} { } -//Node::Node(Args&&... args): _handle{std::forward(args)...} { -//} +// Constructor +template +Node::Node( + const std::string& name, + Topology* topology, + Node* parent, + size_t join_counter, + Args&&... args +) : + _name {name}, + _topology {topology}, + _parent {parent}, + _join_counter {join_counter}, + _handle {std::forward(args)...} { +} + +// Constructor +template +Node::Node( + const TaskParams& params, + Topology* topology, + Node* parent, + size_t join_counter, + Args&&... args +) : + _name {params.name}, + _priority {params.priority}, + _data {params.data}, + _topology {topology}, + _parent {parent}, + _join_counter {join_counter}, + _handle {std::forward(args)...} { +} + +// Constructor +template +Node::Node( + const DefaultTaskParams&, + Topology* topology, + Node* parent, + size_t join_counter, + Args&&... args +) : + _topology {topology}, + _parent {parent}, + _join_counter {join_counter}, + _handle {std::forward(args)...} { +} // Destructor inline Node::~Node() { // this is to avoid stack overflow - if(_handle.index() == DYNAMIC) { + if(_handle.index() == SUBFLOW) { // using std::get_if instead of std::get makes this compatible // with older macOS versions // the result of std::get_if is guaranteed to be non-null // due to the index check above - auto& subgraph = std::get_if(&_handle)->subgraph; + auto& subgraph = std::get_if(&_handle)->subgraph; std::vector nodes; nodes.reserve(subgraph.size()); @@ -779,8 +928,8 @@ inline Node::~Node() { while(i < nodes.size()) { - if(nodes[i]->_handle.index() == DYNAMIC) { - auto& sbg = std::get_if(&(nodes[i]->_handle))->subgraph; + if(nodes[i]->_handle.index() == SUBFLOW) { + auto& sbg = std::get_if(&(nodes[i]->_handle))->subgraph; std::move( sbg._nodes.begin(), sbg._nodes.end(), std::back_inserter(nodes) ); @@ -849,8 +998,11 @@ inline bool Node::_is_conditioner() const { } // Function: _is_cancelled +// we currently only support cancellation of taskflow (no async task) inline bool Node::_is_cancelled() const { - return _topology && _topology->_is_cancelled.load(std::memory_order_relaxed); + //return _topology && _topology->_is_cancelled.load(std::memory_order_relaxed); + return _topology && + (_topology->_state.load(std::memory_order_relaxed) & Topology::CANCELLED); } // Procedure: _set_up_join_counter @@ -865,9 +1017,17 @@ inline void Node::_set_up_join_counter() { c++; } } - _join_counter.store(c, std::memory_order_release); + _join_counter.store(c, std::memory_order_relaxed); } +// Procedure: _process_exception +inline void Node::_process_exception() { + if(_exception_ptr) { + auto e = _exception_ptr; + _exception_ptr = nullptr; + std::rethrow_exception(e); + } +} // Function: _acquire_all inline bool Node::_acquire_all(SmallVector& nodes) { @@ -995,4 +1155,15 @@ Node* Graph::_emplace_back(ArgsT&&... args) { return _nodes.back(); } + } // end of namespace tf. --------------------------------------------------- + + + + + + + + + + diff --git a/bundled/taskflow-3.6.0/include/taskflow/core/notifier.hpp b/bundled/taskflow-3.7.0/taskflow/core/notifier.hpp similarity index 87% rename from bundled/taskflow-3.6.0/include/taskflow/core/notifier.hpp rename to bundled/taskflow-3.7.0/taskflow/core/notifier.hpp index 39bcf6495f..61663798a0 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/core/notifier.hpp +++ b/bundled/taskflow-3.7.0/taskflow/core/notifier.hpp @@ -70,15 +70,20 @@ class Notifier { struct Waiter { std::atomic next; - std::mutex mu; - std::condition_variable cv; uint64_t epoch; - unsigned state; - enum { - kNotSignaled, + enum : unsigned { + kNotSignaled = 0, kWaiting, kSignaled, }; + +#ifdef __cpp_lib_atomic_wait + std::atomic state {0}; +#else + std::mutex mu; + std::condition_variable cv; + unsigned state; +#endif }; explicit Notifier(size_t N) : _waiters{N} { @@ -101,8 +106,13 @@ class Notifier { } // commit_wait commits waiting. + // only the waiter itself can call void commit_wait(Waiter* w) { +#ifdef __cpp_lib_atomic_wait + w->state.store(Waiter::kNotSignaled, std::memory_order_relaxed); +#else w->state = Waiter::kNotSignaled; +#endif // Modification epoch of this waiter. uint64_t epoch = (w->epoch & kEpochMask) + @@ -137,8 +147,8 @@ class Notifier { // cancel_wait cancels effects of the previous prepare_wait call. void cancel_wait(Waiter* w) { uint64_t epoch = - (w->epoch & kEpochMask) + - (((w->epoch & kWaiterMask) >> kWaiterShift) << kEpochShift); + (w->epoch & kEpochMask) + + (((w->epoch & kWaiterMask) >> kWaiterShift) << kEpochShift); uint64_t state = _state.load(std::memory_order_relaxed); for (;;) { if (int64_t((state & kEpochMask) - epoch) < 0) { @@ -165,8 +175,9 @@ class Notifier { uint64_t state = _state.load(std::memory_order_acquire); for (;;) { // Easy case: no waiters. - if ((state & kStackMask) == kStackMask && (state & kWaiterMask) == 0) + if ((state & kStackMask) == kStackMask && (state & kWaiterMask) == 0) { return; + } uint64_t waiters = (state & kWaiterMask) >> kWaiterShift; uint64_t newstate; if (all) { @@ -181,7 +192,9 @@ class Notifier { Waiter* wnext = w->next.load(std::memory_order_relaxed); uint64_t next = kStackMask; //if (wnext != nullptr) next = wnext - &_waiters[0]; - if (wnext != nullptr) next = static_cast(wnext - &_waiters[0]); + if (wnext != nullptr) { + next = static_cast(wnext - &_waiters[0]); + } // Note: we don't add kEpochInc here. ABA problem on the lock-free stack // can't happen because a waiter is re-pushed onto the stack only after // it was in the pre-wait state which inevitably leads to epoch @@ -193,7 +206,9 @@ class Notifier { if (!all && waiters) return; // unblocked pre-wait thread if ((state & kStackMask) == kStackMask) return; Waiter* w = &_waiters[state & kStackMask]; - if (!all) w->next.store(nullptr, std::memory_order_relaxed); + if (!all) { + w->next.store(nullptr, std::memory_order_relaxed); + } _unpark(w); return; } @@ -237,17 +252,35 @@ class Notifier { std::vector _waiters; void _park(Waiter* w) { +#ifdef __cpp_lib_atomic_wait + unsigned target = Waiter::kNotSignaled; + if(w->state.compare_exchange_strong(target, Waiter::kWaiting, + std::memory_order_relaxed, + std::memory_order_relaxed)) { + w->state.wait(Waiter::kWaiting, std::memory_order_relaxed); + } +#else std::unique_lock lock(w->mu); while (w->state != Waiter::kSignaled) { w->state = Waiter::kWaiting; w->cv.wait(lock); } +#endif } void _unpark(Waiter* waiters) { Waiter* next = nullptr; for (Waiter* w = waiters; w; w = next) { next = w->next.load(std::memory_order_relaxed); +#ifdef __cpp_lib_atomic_wait + // We only notify if the other is waiting - this is why we use tri-state + // variable instead of binary-state variable (i.e., atomic_flag) + // Performance is about 0.1% faster + if(w->state.exchange(Waiter::kSignaled, std::memory_order_relaxed) == + Waiter::kWaiting) { + w->state.notify_one(); + } +#else unsigned state; { std::unique_lock lock(w->mu); @@ -256,6 +289,7 @@ class Notifier { } // Avoid notifying if it wasn't waiting. if (state == Waiter::kWaiting) w->cv.notify_one(); +#endif } } diff --git a/bundled/taskflow-3.6.0/include/taskflow/core/observer.hpp b/bundled/taskflow-3.7.0/taskflow/core/observer.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/core/observer.hpp rename to bundled/taskflow-3.7.0/taskflow/core/observer.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/core/semaphore.hpp b/bundled/taskflow-3.7.0/taskflow/core/semaphore.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/core/semaphore.hpp rename to bundled/taskflow-3.7.0/taskflow/core/semaphore.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/core/task.hpp b/bundled/taskflow-3.7.0/taskflow/core/task.hpp similarity index 97% rename from bundled/taskflow-3.6.0/include/taskflow/core/task.hpp rename to bundled/taskflow-3.7.0/taskflow/core/task.hpp index cd10b73117..1070671c40 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/core/task.hpp +++ b/bundled/taskflow-3.7.0/taskflow/core/task.hpp @@ -24,7 +24,7 @@ enum class TaskType : int { /** @brief static task type */ STATIC, /** @brief dynamic (subflow) task type */ - DYNAMIC, + SUBFLOW, /** @brief condition task type */ CONDITION, /** @brief module task type */ @@ -42,7 +42,7 @@ enum class TaskType : int { inline constexpr std::array TASK_TYPES = { TaskType::PLACEHOLDER, TaskType::STATIC, - TaskType::DYNAMIC, + TaskType::SUBFLOW, TaskType::CONDITION, TaskType::MODULE, TaskType::ASYNC, @@ -56,7 +56,7 @@ The name of each task type is the litte-case string of its characters. @code{.cpp} TaskType::PLACEHOLDER -> "placeholder" TaskType::STATIC -> "static" -TaskType::DYNAMIC -> "subflow" +TaskType::SUBFLOW -> "subflow" TaskType::CONDITION -> "condition" TaskType::MODULE -> "module" TaskType::ASYNC -> "async" @@ -69,7 +69,7 @@ inline const char* to_string(TaskType type) { switch(type) { case TaskType::PLACEHOLDER: val = "placeholder"; break; case TaskType::STATIC: val = "static"; break; - case TaskType::DYNAMIC: val = "subflow"; break; + case TaskType::SUBFLOW: val = "subflow"; break; case TaskType::CONDITION: val = "condition"; break; case TaskType::MODULE: val = "module"; break; case TaskType::ASYNC: val = "async"; break; @@ -89,7 +89,7 @@ inline const char* to_string(TaskType type) { A dynamic task is a callable object constructible from std::function. */ template -constexpr bool is_dynamic_task_v = +constexpr bool is_subflow_task_v = std::is_invocable_r_v && !std::is_invocable_r_v; @@ -102,7 +102,7 @@ or std::function. template constexpr bool is_condition_task_v = (std::is_invocable_r_v || std::is_invocable_r_v) && - !is_dynamic_task_v; + !is_subflow_task_v; /** @brief determines if a callable is a multi-condition task @@ -115,7 +115,7 @@ template constexpr bool is_multi_condition_task_v = (std::is_invocable_r_v, C> || std::is_invocable_r_v, C, Runtime&>) && - !is_dynamic_task_v; + !is_subflow_task_v; /** @brief determines if a callable is a static task @@ -128,7 +128,7 @@ constexpr bool is_static_task_v = (std::is_invocable_r_v || std::is_invocable_r_v) && !is_condition_task_v && !is_multi_condition_task_v && - !is_dynamic_task_v; + !is_subflow_task_v; // ---------------------------------------------------------------------------- // Task @@ -307,7 +307,7 @@ class Task { @return @c *this */ Task& data(void* data); - + /** @brief assigns a priority value to the task @@ -512,7 +512,7 @@ inline TaskType Task::type() const { switch(_node->_handle.index()) { case Node::PLACEHOLDER: return TaskType::PLACEHOLDER; case Node::STATIC: return TaskType::STATIC; - case Node::DYNAMIC: return TaskType::DYNAMIC; + case Node::SUBFLOW: return TaskType::SUBFLOW; case Node::CONDITION: return TaskType::CONDITION; case Node::MULTI_CONDITION: return TaskType::CONDITION; case Node::MODULE: return TaskType::MODULE; @@ -558,8 +558,8 @@ Task& Task::work(C&& c) { if constexpr(is_static_task_v) { _node->_handle.emplace(std::forward(c)); } - else if constexpr(is_dynamic_task_v) { - _node->_handle.emplace(std::forward(c)); + else if constexpr(is_subflow_task_v) { + _node->_handle.emplace(std::forward(c)); } else if constexpr(is_condition_task_v) { _node->_handle.emplace(std::forward(c)); @@ -711,7 +711,7 @@ inline TaskType TaskView::type() const { switch(_node._handle.index()) { case Node::PLACEHOLDER: return TaskType::PLACEHOLDER; case Node::STATIC: return TaskType::STATIC; - case Node::DYNAMIC: return TaskType::DYNAMIC; + case Node::SUBFLOW: return TaskType::SUBFLOW; case Node::CONDITION: return TaskType::CONDITION; case Node::MULTI_CONDITION: return TaskType::CONDITION; case Node::MODULE: return TaskType::MODULE; @@ -742,7 +742,7 @@ void TaskView::for_each_dependent(V&& visitor) const { } } -} // end of namespace tf. --------------------------------------------------- +} // end of namespace tf. ---------------------------------------------------- namespace std { diff --git a/bundled/taskflow-3.6.0/include/taskflow/core/taskflow.hpp b/bundled/taskflow-3.7.0/taskflow/core/taskflow.hpp similarity index 88% rename from bundled/taskflow-3.6.0/include/taskflow/core/taskflow.hpp rename to bundled/taskflow-3.7.0/taskflow/core/taskflow.hpp index ff836f5fbd..f6a0f424a2 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/core/taskflow.hpp +++ b/bundled/taskflow-3.7.0/taskflow/core/taskflow.hpp @@ -22,7 +22,7 @@ dependency between two tasks. A task is one of the following types: 1. static task : the callable constructible from @c std::function - 2. dynamic task : the callable constructible from + 2. subflow task : the callable constructible from @c std::function 3. condition task : the callable constructible from @c std::function @@ -246,6 +246,32 @@ class Taskflow : public FlowBuilder { template void for_each_task(V&& visitor) const; + /** + @brief removes dependencies that go from task @c from to task @c to + + @param from from task (dependent) + @param to to task (successor) + + @code{.cpp} + tf::Taskflow taskflow; + auto a = taskflow.placeholder().name("a"); + auto b = taskflow.placeholder().name("b"); + auto c = taskflow.placeholder().name("c"); + auto d = taskflow.placeholder().name("d"); + + a.precede(b, c, d); + assert(a.num_successors() == 3); + assert(b.num_dependents() == 1); + assert(c.num_dependents() == 1); + assert(d.num_dependents() == 1); + + taskflow.remove_dependency(a, b); + assert(a.num_successors() == 2); + assert(b.num_dependents() == 0); + @endcode + */ + inline void remove_dependency(Task from, Task to); + /** @brief returns a reference to the underlying graph object @@ -264,7 +290,6 @@ class Taskflow : public FlowBuilder { Graph _graph; std::queue> _topologies; - std::optional::iterator> _satellite; void _dump(std::ostream&, const Graph*) const; @@ -346,6 +371,21 @@ void Taskflow::for_each_task(V&& visitor) const { } } +// Procedure: remove_dependency +inline void Taskflow::remove_dependency(Task from, Task to) { + from._node->_successors.erase(std::remove_if( + from._node->_successors.begin(), from._node->_successors.end(), [&](Node* i){ + return i == to._node; + } + ), from._node->_successors.end()); + + to._node->_dependents.erase(std::remove_if( + to._node->_dependents.begin(), to._node->_dependents.end(), [&](Node* i){ + return i == from._node; + } + ), to._node->_dependents.end()); +} + // Procedure: dump inline std::string Taskflow::dump() const { std::ostringstream oss; @@ -429,7 +469,7 @@ inline void Taskflow::_dump( } // subflow join node - if(node->_parent && node->_parent->_handle.index() == Node::DYNAMIC && + if(node->_parent && node->_parent->_handle.index() == Node::SUBFLOW && node->_successors.size() == 0 ) { os << 'p' << node << " -> p" << node->_parent << ";\n"; @@ -438,8 +478,8 @@ inline void Taskflow::_dump( // node info switch(node->_handle.index()) { - case Node::DYNAMIC: { - auto& sbg = std::get_if(&node->_handle)->subgraph; + case Node::SUBFLOW: { + auto& sbg = std::get_if(&node->_handle)->subgraph; if(!sbg.empty()) { os << "subgraph cluster_p" << node << " {\nlabel=\"Subflow: "; if(node->_name.empty()) os << 'p' << node; @@ -502,7 +542,6 @@ inline void Taskflow::_dump( tf::Future is a derived class from std::future that will eventually hold the execution result of a submitted taskflow (tf::Executor::run) -or an asynchronous task (tf::Executor::async, tf::Executor::silent_async). In addition to the base methods inherited from std::future, you can call tf::Future::cancel to cancel the execution of the running taskflow associated with this future object. @@ -536,10 +575,6 @@ class Future : public std::future { friend class Subflow; friend class Runtime; - using handle_t = std::variant< - std::monostate, std::weak_ptr - >; - public: /** @@ -582,37 +617,26 @@ class Future : public std::future { bool cancel(); private: + + std::weak_ptr _topology; - handle_t _handle; - - template - Future(std::future&&, P&&); + Future(std::future&&, std::weak_ptr = std::weak_ptr()); }; template -template -Future::Future(std::future&& fu, P&& p) : - std::future {std::move(fu)}, - _handle {std::forward

(p)} { +Future::Future(std::future&& f, std::weak_ptr p) : + std::future {std::move(f)}, + _topology {std::move(p)} { } // Function: cancel template bool Future::cancel() { - return std::visit([](auto&& arg){ - using P = std::decay_t; - if constexpr(std::is_same_v) { - return false; - } - else { - auto ptr = arg.lock(); - if(ptr) { - ptr->_is_cancelled.store(true, std::memory_order_relaxed); - return true; - } - return false; - } - }, _handle); + if(auto ptr = _topology.lock(); ptr) { + ptr->_state.fetch_or(Topology::CANCELLED, std::memory_order_relaxed); + return true; + } + return false; } diff --git a/bundled/taskflow-3.6.0/include/taskflow/core/topology.hpp b/bundled/taskflow-3.7.0/taskflow/core/topology.hpp similarity index 57% rename from bundled/taskflow-3.6.0/include/taskflow/core/topology.hpp rename to bundled/taskflow-3.7.0/taskflow/core/topology.hpp index b4d9eab2e0..335ccfb80a 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/core/topology.hpp +++ b/bundled/taskflow-3.7.0/taskflow/core/topology.hpp @@ -4,33 +4,31 @@ namespace tf { // ---------------------------------------------------------------------------- -// class: TopologyBase class TopologyBase { - friend class Executor; - friend class Node; - - template - friend class Future; - - protected: - - std::atomic _is_cancelled { false }; }; -// ---------------------------------------------------------------------------- - // class: Topology -class Topology : public TopologyBase { +class Topology { friend class Executor; friend class Runtime; + friend class Node; + + template + friend class Future; + + constexpr static int CLEAN = 0; + constexpr static int CANCELLED = 1; + constexpr static int EXCEPTION = 2; public: template Topology(Taskflow&, P&&, C&&); + bool cancelled() const; + private: Taskflow& _taskflow; @@ -43,6 +41,11 @@ class Topology : public TopologyBase { std::function _call; std::atomic _join_counter {0}; + std::atomic _state {CLEAN}; + + std::exception_ptr _exception_ptr {nullptr}; + + void _carry_out_promise(); }; // Constructor @@ -53,4 +56,21 @@ Topology::Topology(Taskflow& tf, P&& p, C&& c): _call {std::forward(c)} { } +// Procedure +inline void Topology::_carry_out_promise() { + if(_exception_ptr) { + auto e = _exception_ptr; + _exception_ptr = nullptr; + _promise.set_exception(e); + } + else { + _promise.set_value(); + } +} + +// Function: cancelled +inline bool Topology::cancelled() const { + return _state.load(std::memory_order_relaxed) & CANCELLED; +} + } // end of namespace tf. ---------------------------------------------------- diff --git a/bundled/taskflow-3.6.0/include/taskflow/core/tsq.hpp b/bundled/taskflow-3.7.0/taskflow/core/tsq.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/core/tsq.hpp rename to bundled/taskflow-3.7.0/taskflow/core/tsq.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/core/worker.hpp b/bundled/taskflow-3.7.0/taskflow/core/worker.hpp similarity index 62% rename from bundled/taskflow-3.6.0/include/taskflow/core/worker.hpp rename to bundled/taskflow-3.7.0/taskflow/core/worker.hpp index 47fcf819d6..8f86381a8f 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/core/worker.hpp +++ b/bundled/taskflow-3.7.0/taskflow/core/worker.hpp @@ -167,94 +167,6 @@ inline size_t WorkerView::queue_capacity() const { } -// ---------------------------------------------------------------------------- -// Class Definition: WorkerInterface -// ---------------------------------------------------------------------------- - -/** -@class WorkerInterface - -@brief class to configure worker behavior in an executor - -The tf::WorkerInterface class lets users interact with the executor -to customize the worker behavior, -such as calling custom methods before and after a worker enters and leaves -the loop. -When you create an executor, it spawns a set of workers to run tasks. -The interaction between the executor and its spawned workers looks like -the following: - -for(size_t n=0; n -std::shared_ptr make_worker_interface(ArgsT&&... args) { - static_assert( - std::is_base_of_v, - "T must be derived from WorkerInterface" - ); - return std::make_shared(std::forward(args)...); -} - } // end of namespact tf ----------------------------------------------------- diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/find.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/algorithm/find.hpp similarity index 99% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/find.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/algorithm/find.hpp index 9d5b10fd55..f344666950 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/find.hpp +++ b/bundled/taskflow-3.7.0/taskflow/cuda/algorithm/find.hpp @@ -35,7 +35,6 @@ void cuda_find_if_loop(P&& p, I input, unsigned count, unsigned* idx, U pred) { // set the index to the maximum cuda_single_task(p, [=] __device__ () { *idx = count; }); -ls // launch the kernel to atomic-find the minimum cuda_kernel<<>>([=] __device__ (auto tid, auto bid) { diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/for_each.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/algorithm/for_each.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/for_each.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/algorithm/for_each.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/matmul.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/algorithm/matmul.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/matmul.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/algorithm/matmul.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/merge.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/algorithm/merge.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/merge.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/algorithm/merge.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/reduce.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/algorithm/reduce.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/reduce.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/algorithm/reduce.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/scan.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/algorithm/scan.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/scan.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/algorithm/scan.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/sort.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/algorithm/sort.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/sort.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/algorithm/sort.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/transform.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/algorithm/transform.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/transform.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/algorithm/transform.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/transpose.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/algorithm/transpose.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/transpose.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/algorithm/transpose.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_capturer.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/cuda_capturer.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_capturer.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/cuda_capturer.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_device.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/cuda_device.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_device.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/cuda_device.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_error.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/cuda_error.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_error.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/cuda_error.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_execution_policy.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/cuda_execution_policy.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_execution_policy.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/cuda_execution_policy.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_graph.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/cuda_graph.hpp similarity index 98% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_graph.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/cuda_graph.hpp index f239c8d431..a326aedeae 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_graph.hpp +++ b/bundled/taskflow-3.7.0/taskflow/cuda/cuda_graph.hpp @@ -248,12 +248,12 @@ inline const char* cuda_graph_node_type_to_string(cudaGraphNodeType type) { @param graph native CUDA graph */ template -void cuda_dump_graph(T& os, cudaGraph_t graph) { +void cuda_dump_graph(T& os, cudaGraph_t g) { os << "digraph cudaGraph {\n"; std::stack> stack; - stack.push(std::make_tuple(graph, nullptr, 1)); + stack.push(std::make_tuple(g, nullptr, 1)); int pl = 0; @@ -281,9 +281,9 @@ void cuda_dump_graph(T& os, cudaGraph_t graph) { auto type = cuda_get_graph_node_type(node); if(type == cudaGraphNodeTypeGraph) { - cudaGraph_t graph; - TF_CHECK_CUDA(cudaGraphChildGraphNodeGetGraph(node, &graph), ""); - stack.push(std::make_tuple(graph, node, l+1)); + cudaGraph_t child_graph; + TF_CHECK_CUDA(cudaGraphChildGraphNodeGetGraph(node, &child_graph), ""); + stack.push(std::make_tuple(child_graph, node, l+1)); os << 'p' << node << "[" << "shape=folder, style=filled, fontcolor=white, fillcolor=purple, " @@ -635,8 +635,8 @@ cudaFlowNode::Kernel::Kernel(F&& f) : // Capture handle constructor template -cudaFlowNode::Capture::Capture(C&& work) : - work {std::forward(work)} { +cudaFlowNode::Capture::Capture(C&& c) : + work {std::forward(c)} { } // Constructor diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_memory.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/cuda_memory.hpp similarity index 99% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_memory.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/cuda_memory.hpp index 0740d491e3..44648683e7 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_memory.hpp +++ b/bundled/taskflow-3.7.0/taskflow/cuda/cuda_memory.hpp @@ -498,7 +498,7 @@ class cudaDeviceAllocator { @param n number of elements (each of size sizeof(value_type)) to be allocated @return a pointer to the initial element in the block of storage. */ - pointer allocate( size_type n, std::allocator::const_pointer = 0 ) + pointer allocate( size_type n, const void* = 0 ) { void* ptr = NULL; TF_CHECK_CUDA( @@ -694,7 +694,7 @@ class cudaUSMAllocator { @param n number of elements (each of size sizeof(value_type)) to be allocated @return a pointer to the initial element in the block of storage. */ - pointer allocate( size_type n, std::allocator::const_pointer = 0 ) + pointer allocate( size_type n, const void* = 0 ) { void* ptr {nullptr}; TF_CHECK_CUDA( diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_meta.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/cuda_meta.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_meta.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/cuda_meta.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_object.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/cuda_object.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_object.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/cuda_object.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_optimizer.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/cuda_optimizer.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_optimizer.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/cuda_optimizer.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_stream.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/cuda_stream.hpp similarity index 98% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_stream.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/cuda_stream.hpp index f3e48f145f..1e312605be 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_stream.hpp +++ b/bundled/taskflow-3.7.0/taskflow/cuda/cuda_stream.hpp @@ -185,7 +185,9 @@ struct cudaEventCreator { */ struct cudaEventDeleter { void operator () (cudaEvent_t event) const { - cudaEventDestroy(event); + if (event != nullptr) { + cudaEventDestroy(event); + } } }; diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_task.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/cuda_task.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_task.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/cuda_task.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/cuda/cudaflow.hpp b/bundled/taskflow-3.7.0/taskflow/cuda/cudaflow.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/cuda/cudaflow.hpp rename to bundled/taskflow-3.7.0/taskflow/cuda/cudaflow.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/dsl/connection.hpp b/bundled/taskflow-3.7.0/taskflow/dsl/connection.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/dsl/connection.hpp rename to bundled/taskflow-3.7.0/taskflow/dsl/connection.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/dsl/dsl.hpp b/bundled/taskflow-3.7.0/taskflow/dsl/dsl.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/dsl/dsl.hpp rename to bundled/taskflow-3.7.0/taskflow/dsl/dsl.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/dsl/meta_macro.hpp b/bundled/taskflow-3.7.0/taskflow/dsl/meta_macro.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/dsl/meta_macro.hpp rename to bundled/taskflow-3.7.0/taskflow/dsl/meta_macro.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/dsl/task_analyzer.hpp b/bundled/taskflow-3.7.0/taskflow/dsl/task_analyzer.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/dsl/task_analyzer.hpp rename to bundled/taskflow-3.7.0/taskflow/dsl/task_analyzer.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/dsl/task_dsl.hpp b/bundled/taskflow-3.7.0/taskflow/dsl/task_dsl.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/dsl/task_dsl.hpp rename to bundled/taskflow-3.7.0/taskflow/dsl/task_dsl.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/dsl/task_trait.hpp b/bundled/taskflow-3.7.0/taskflow/dsl/task_trait.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/dsl/task_trait.hpp rename to bundled/taskflow-3.7.0/taskflow/dsl/task_trait.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/dsl/tuple_utils.hpp b/bundled/taskflow-3.7.0/taskflow/dsl/tuple_utils.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/dsl/tuple_utils.hpp rename to bundled/taskflow-3.7.0/taskflow/dsl/tuple_utils.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/dsl/type_list.hpp b/bundled/taskflow-3.7.0/taskflow/dsl/type_list.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/dsl/type_list.hpp rename to bundled/taskflow-3.7.0/taskflow/dsl/type_list.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/sycl/algorithm/reduce.hpp b/bundled/taskflow-3.7.0/taskflow/sycl/algorithm/reduce.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/sycl/algorithm/reduce.hpp rename to bundled/taskflow-3.7.0/taskflow/sycl/algorithm/reduce.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/sycl/algorithm/sycl_for_each.hpp b/bundled/taskflow-3.7.0/taskflow/sycl/algorithm/sycl_for_each.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/sycl/algorithm/sycl_for_each.hpp rename to bundled/taskflow-3.7.0/taskflow/sycl/algorithm/sycl_for_each.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/sycl/algorithm/sycl_transform.hpp b/bundled/taskflow-3.7.0/taskflow/sycl/algorithm/sycl_transform.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/sycl/algorithm/sycl_transform.hpp rename to bundled/taskflow-3.7.0/taskflow/sycl/algorithm/sycl_transform.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/sycl/sycl_execution_policy.hpp b/bundled/taskflow-3.7.0/taskflow/sycl/sycl_execution_policy.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/sycl/sycl_execution_policy.hpp rename to bundled/taskflow-3.7.0/taskflow/sycl/sycl_execution_policy.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/sycl/sycl_graph.hpp b/bundled/taskflow-3.7.0/taskflow/sycl/sycl_graph.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/sycl/sycl_graph.hpp rename to bundled/taskflow-3.7.0/taskflow/sycl/sycl_graph.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/sycl/sycl_meta.hpp b/bundled/taskflow-3.7.0/taskflow/sycl/sycl_meta.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/sycl/sycl_meta.hpp rename to bundled/taskflow-3.7.0/taskflow/sycl/sycl_meta.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/sycl/sycl_task.hpp b/bundled/taskflow-3.7.0/taskflow/sycl/sycl_task.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/sycl/sycl_task.hpp rename to bundled/taskflow-3.7.0/taskflow/sycl/sycl_task.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/sycl/syclflow.hpp b/bundled/taskflow-3.7.0/taskflow/sycl/syclflow.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/sycl/syclflow.hpp rename to bundled/taskflow-3.7.0/taskflow/sycl/syclflow.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/taskflow.hpp b/bundled/taskflow-3.7.0/taskflow/taskflow.hpp similarity index 93% rename from bundled/taskflow-3.6.0/include/taskflow/taskflow.hpp rename to bundled/taskflow-3.7.0/taskflow/taskflow.hpp index 38ac741698..c2403f81f5 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/taskflow.hpp +++ b/bundled/taskflow-3.7.0/taskflow/taskflow.hpp @@ -33,8 +33,8 @@ // TF_VERSION / 100 % 1000 is the minor version // TF_VERSION / 100000 is the major version -// current version: 3.6.0 -#define TF_VERSION 300600 +// current version: 3.7.0 +#define TF_VERSION 300700 #define TF_MAJOR_VERSION TF_VERSION/100000 #define TF_MINOR_VERSION TF_VERSION/100%1000 @@ -57,7 +57,7 @@ namespace detail { } Release notes are available here: https://taskflow.github.io/taskflow/Releases.html */ constexpr const char* version() { - return "3.6.0"; + return "3.7.0"; } diff --git a/bundled/taskflow-3.6.0/include/taskflow/utility/iterator.hpp b/bundled/taskflow-3.7.0/taskflow/utility/iterator.hpp similarity index 81% rename from bundled/taskflow-3.6.0/include/taskflow/utility/iterator.hpp rename to bundled/taskflow-3.7.0/taskflow/utility/iterator.hpp index 6441391f94..8636a3bcce 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/utility/iterator.hpp +++ b/bundled/taskflow-3.7.0/taskflow/utility/iterator.hpp @@ -9,8 +9,8 @@ template constexpr std::enable_if_t>::value, bool> is_range_invalid(T beg, T end, T step) { return ((step == 0 && beg != end) || - (beg < end && step <= 0) || - (beg > end && step >= 0)); + (beg < end && step <= 0) || // positive range + (beg > end && step >= 0)); // negative range } template diff --git a/bundled/taskflow-3.7.0/taskflow/utility/macros.hpp b/bundled/taskflow-3.7.0/taskflow/utility/macros.hpp new file mode 100644 index 0000000000..f184468c51 --- /dev/null +++ b/bundled/taskflow-3.7.0/taskflow/utility/macros.hpp @@ -0,0 +1,33 @@ +#pragma once + +#if defined(_MSC_VER) + #define TF_FORCE_INLINE __forceinline +#elif defined(__GNUC__) && __GNUC__ > 3 + #define TF_FORCE_INLINE __attribute__((__always_inline__)) inline +#else + #define TF_FORCE_INLINE inline +#endif + +#if defined(_MSC_VER) + #define TF_NO_INLINE __declspec(noinline) +#elif defined(__GNUC__) && __GNUC__ > 3 + #define TF_NO_INLINE __attribute__((__noinline__)) +#else + #define TF_NO_INLINE +#endif + +// ---------------------------------------------------------------------------- + +#ifdef TF_DISABLE_EXCEPTION_HANDLING + #define TF_EXECUTOR_EXCEPTION_HANDLER(worker, node, code_block) \ + code_block; +#else + #define TF_EXECUTOR_EXCEPTION_HANDLER(worker, node, code_block) \ + try { \ + code_block; \ + } catch(...) { \ + _process_exception(worker, node); \ + } +#endif + +// ---------------------------------------------------------------------------- diff --git a/bundled/taskflow-3.6.0/include/taskflow/utility/math.hpp b/bundled/taskflow-3.7.0/taskflow/utility/math.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/utility/math.hpp rename to bundled/taskflow-3.7.0/taskflow/utility/math.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/utility/object_pool.hpp b/bundled/taskflow-3.7.0/taskflow/utility/object_pool.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/utility/object_pool.hpp rename to bundled/taskflow-3.7.0/taskflow/utility/object_pool.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/utility/os.hpp b/bundled/taskflow-3.7.0/taskflow/utility/os.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/utility/os.hpp rename to bundled/taskflow-3.7.0/taskflow/utility/os.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/utility/serializer.hpp b/bundled/taskflow-3.7.0/taskflow/utility/serializer.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/utility/serializer.hpp rename to bundled/taskflow-3.7.0/taskflow/utility/serializer.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/utility/singleton.hpp b/bundled/taskflow-3.7.0/taskflow/utility/singleton.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/utility/singleton.hpp rename to bundled/taskflow-3.7.0/taskflow/utility/singleton.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/utility/small_vector.hpp b/bundled/taskflow-3.7.0/taskflow/utility/small_vector.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/utility/small_vector.hpp rename to bundled/taskflow-3.7.0/taskflow/utility/small_vector.hpp diff --git a/bundled/taskflow-3.6.0/include/taskflow/utility/stream.hpp b/bundled/taskflow-3.7.0/taskflow/utility/stream.hpp similarity index 97% rename from bundled/taskflow-3.6.0/include/taskflow/utility/stream.hpp rename to bundled/taskflow-3.7.0/taskflow/utility/stream.hpp index 320aa6c63d..34a86ff569 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/utility/stream.hpp +++ b/bundled/taskflow-3.7.0/taskflow/utility/stream.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace tf { diff --git a/bundled/taskflow-3.6.0/include/taskflow/utility/traits.hpp b/bundled/taskflow-3.7.0/taskflow/utility/traits.hpp similarity index 99% rename from bundled/taskflow-3.6.0/include/taskflow/utility/traits.hpp rename to bundled/taskflow-3.7.0/taskflow/utility/traits.hpp index 196b147c85..dd3953bd44 100644 --- a/bundled/taskflow-3.6.0/include/taskflow/utility/traits.hpp +++ b/bundled/taskflow-3.7.0/taskflow/utility/traits.hpp @@ -1,5 +1,9 @@ #pragma once +#if __has_include() +# include +#endif + #include #include #include diff --git a/bundled/taskflow-3.6.0/include/taskflow/utility/uuid.hpp b/bundled/taskflow-3.7.0/taskflow/utility/uuid.hpp similarity index 100% rename from bundled/taskflow-3.6.0/include/taskflow/utility/uuid.hpp rename to bundled/taskflow-3.7.0/taskflow/utility/uuid.hpp