| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 1f3084f commit 06d13fd
11 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,7 +2,7 @@ list(APPEND TF_EXAMPLES | |||
| 2 | 2 | simple | |
| 3 | 3 | attach_data | |
| 4 | 4 | async | |
| 5 | - subflow_async | ||
| 5 | + runtime_async | ||
| 6 | 6 | dependent_async | |
| 7 | 7 | dependent_async_algorithm | |
| 8 | 8 | observer | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -35,11 +35,11 @@ int main() { | |||
| 35 | 35 | ||
| 36 | 36 | std::atomic<int> counter {0}; | |
| 37 | 37 | ||
| 38 | - taskflow.emplace([&](tf::Subflow& sf){ | ||
| 38 | + taskflow.emplace([&](tf::Runtime& rt){ | ||
| 39 | 39 | for(int i=0; i<100; i++) { | |
| 40 | - sf.silent_async([&](){ counter.fetch_add(1, std::memory_order_relaxed); }); | ||
| 40 | + rt.silent_async([&](){ counter.fetch_add(1, std::memory_order_relaxed); }); | ||
| 41 | 41 | } | |
| 42 | - sf.join(); | ||
| 42 | + rt.corun_all(); | ||
| 43 | 43 | ||
| 44 | 44 | // when subflow joins, all spawned tasks from the subflow will finish | |
| 45 | 45 | if(counter == 100) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,18 +9,18 @@ int main() { | |||
| 9 | 9 | ||
| 10 | 10 | std::atomic<int> counter{0}; | |
| 11 | 11 | ||
| 12 | - taskflow.emplace([&](tf::Subflow& sf){ | ||
| 12 | + taskflow.emplace([&](tf::Runtime& rt){ | ||
| 13 | 13 | for(int i=0; i<10; i++) { | |
| 14 | 14 | // Here, we use "silent_async" instead of "async" because we do | |
| 15 | 15 | // not care the return value. The method "silent_async" gives us | |
| 16 | 16 | // less overhead compared to "async". | |
| 17 | 17 | // The 10 asynchronous tasks run concurrently. | |
| 18 | - sf.silent_async([&](){ | ||
| 18 | + rt.silent_async([&](){ | ||
| 19 | 19 | std::cout << "async task from the subflow\n"; | |
| 20 | 20 | counter.fetch_add(1, std::memory_order_relaxed); | |
| 21 | 21 | }); | |
| 22 | 22 | } | |
| 23 | - sf.join(); | ||
| 23 | + rt.corun_all(); | ||
| 24 | 24 | std::cout << counter << " = 10\n"; | |
| 25 | 25 | }); | |
| 26 | 26 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -13,7 +13,7 @@ | |||
| 13 | 13 | #include <taskflow/taskflow.hpp> // the only include you need | |
| 14 | 14 | ||
| 15 | 15 | int main(){ | |
| 16 | - | ||
| 16 | + | ||
| 17 | 17 | tf::Executor executor; | |
| 18 | 18 | tf::Taskflow taskflow("simple"); | |
| 19 | 19 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -48,7 +48,7 @@ TF_FORCE_INLINE void launch_loop( | |||
| 48 | 48 | break; | |
| 49 | 49 | } | |
| 50 | 50 | else { | |
| 51 | - rt.silent_async_unchecked([=](){ launch_loop(part, loop); }); | ||
| 51 | + rt.silent_async([=](){ launch_loop(part, loop); }); | ||
| 52 | 52 | } | |
| 53 | 53 | } | |
| 54 | 54 | ||
@@ -69,7 +69,7 @@ TF_FORCE_INLINE void launch_loop( | |||
| 69 | 69 | launch_loop(part, loop); | |
| 70 | 70 | } | |
| 71 | 71 | else { | |
| 72 | - rt.silent_async_unchecked([=](){ launch_loop(part, loop); }); | ||
| 72 | + rt.silent_async([=](){ launch_loop(part, loop); }); | ||
| 73 | 73 | } | |
| 74 | 74 | } | |
| 75 | 75 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1075,10 +1075,9 @@ class Executor { | |||
| 1075 | 1075 | void _explore_task(Worker&, Node*&); | |
| 1076 | 1076 | void _schedule(Worker&, Node*); | |
| 1077 | 1077 | void _schedule(Node*); | |
| 1078 | - void _schedule(Worker&, const SmallVector<Node*>&); | ||
| 1079 | - void _schedule(const SmallVector<Node*>&); | ||
| 1078 | + void _schedule(Worker&, const std::vector<Node*>&, size_t); | ||
| 1079 | + void _schedule(const std::vector<Node*>&, size_t); | ||
| 1080 | 1080 | void _set_up_topology(Worker*, Topology*); | |
| 1081 | - void _set_up_graph(Graph&, Node*, Topology*, int, SmallVector<Node*>&); | ||
| 1082 | 1081 | void _tear_down_topology(Worker&, Topology*); | |
| 1083 | 1082 | void _tear_down_async(Node*); | |
| 1084 | 1083 | void _tear_down_dependent_async(Worker&, Node*); | |
@@ -1087,7 +1086,6 @@ class Executor { | |||
| 1087 | 1086 | void _decrement_topology(); | |
| 1088 | 1087 | void _invoke(Worker&, Node*); | |
| 1089 | 1088 | void _invoke_static_task(Worker&, Node*); | |
| 1090 | - void _invoke_subflow_task(Worker&, Node*); | ||
| 1091 | 1089 | void _detach_subflow_task(Worker&, Node*, Graph&); | |
| 1092 | 1090 | void _invoke_condition_task(Worker&, Node*, SmallVector<int>&); | |
| 1093 | 1091 | void _invoke_multi_condition_task(Worker&, Node*, SmallVector<int>&); | |
@@ -1098,7 +1096,10 @@ class Executor { | |||
| 1098 | 1096 | void _schedule_async_task(Node*); | |
| 1099 | 1097 | void _corun_graph(Worker&, Node*, Graph&); | |
| 1100 | 1098 | ||
| 1099 | + bool _invoke_subflow_task(Worker&, Node*); | ||
| 1101 | 1100 | bool _invoke_module_task(Worker&, Node*); | |
| 1101 | + | ||
| 1102 | + size_t _set_up_graph(Graph&, Node*, Topology*, int); | ||
| 1102 | 1103 | ||
| 1103 | 1104 | template <typename P> | |
| 1104 | 1105 | void _corun_until(Worker&, P&&); | |
@@ -1434,11 +1435,9 @@ inline void Executor::_schedule(Node* node) { | |||
| 1434 | 1435 | } | |
| 1435 | 1436 | ||
| 1436 | 1437 | // Procedure: _schedule | |
| 1437 | - inline void Executor::_schedule(Worker& worker, const SmallVector<Node*>& nodes) { | ||
| 1438 | + inline void Executor::_schedule(Worker& worker, const std::vector<Node*>& nodes, size_t num_nodes) { | ||
| 1438 | 1439 | ||
| 1439 | - // We need to cacth the node count to avoid accessing the nodes | ||
| 1440 | - // vector while the parent topology is removed! | ||
| 1441 | - const auto num_nodes = nodes.size(); | ||
| 1440 | + //assert(nodes.size() >= num_nodes); | ||
| 1442 | 1441 | ||
| 1443 | 1442 | if(num_nodes == 0) { | |
| 1444 | 1443 | return; | |
@@ -1462,10 +1461,9 @@ inline void Executor::_schedule(Worker& worker, const SmallVector<Node*>& nodes) | |||
| 1462 | 1461 | } | |
| 1463 | 1462 | ||
| 1464 | 1463 | // Procedure: _schedule | |
| 1465 | - inline void Executor::_schedule(const SmallVector<Node*>& nodes) { | ||
| 1466 | - | ||
| 1467 | - // parent topology may be removed! | ||
| 1468 | - const auto num_nodes = nodes.size(); | ||
| 1464 | + inline void Executor::_schedule(const std::vector<Node*>& nodes, size_t num_nodes) { | ||
| 1465 | + | ||
| 1466 | + //assert(nodes.size() >= num_nodes); | ||
| 1469 | 1467 | ||
| 1470 | 1468 | if(num_nodes == 0) { | |
| 1471 | 1469 | return; | |
@@ -1511,7 +1509,9 @@ inline void Executor::_invoke(Worker& worker, Node* node) { | |||
| 1511 | 1509 | ||
| 1512 | 1510 | // subflow task | |
| 1513 | 1511 | case Node::SUBFLOW: { | |
| 1514 | - _invoke_subflow_task(worker, node); | ||
| 1512 | + if(_invoke_subflow_task(worker, node)) { | ||
| 1513 | + return; | ||
| 1514 | + } | ||
| 1515 | 1515 | } | |
| 1516 | 1516 | break; | |
| 1517 | 1517 | ||
@@ -1630,6 +1630,17 @@ inline void Executor::_tear_down_invoke(Worker& worker, Node* node) { | |||
| 1630 | 1630 | } | |
| 1631 | 1631 | else { | |
| 1632 | 1632 | switch(parent->_handle.index()) { | |
| 1633 | + | ||
| 1634 | + case Node::SUBFLOW: { | ||
| 1635 | + if(parent->_join_counter.fetch_sub(1, std::memory_order_acq_rel) == 1) { | ||
| 1636 | + // only preempted parent needs to be re-invoked | ||
| 1637 | + if(parent->_state.load(std::memory_order_relaxed) & Node::PREEMPTED) { | ||
| 1638 | + //assert(worker._cache == nullptr); | ||
| 1639 | + worker._cache = parent; | ||
| 1640 | + } | ||
| 1641 | + } | ||
| 1642 | + } break; | ||
| 1643 | + | ||
| 1633 | 1644 | case Node::MODULE: { | |
| 1634 | 1645 | if(parent->_join_counter.fetch_sub(1, std::memory_order_acq_rel) == 1) { | |
| 1635 | 1646 | //assert(worker._cache == nullptr); | |
@@ -1703,19 +1714,37 @@ inline void Executor::_invoke_static_task(Worker& worker, Node* node) { | |||
| 1703 | 1714 | } | |
| 1704 | 1715 | ||
| 1705 | 1716 | // Procedure: _invoke_subflow_task | |
| 1706 | - inline void Executor::_invoke_subflow_task(Worker& w, Node* node) { | ||
| 1707 | - _observer_prologue(w, node); | ||
| 1708 | - TF_EXECUTOR_EXCEPTION_HANDLER(w, node, { | ||
| 1717 | + inline bool Executor::_invoke_subflow_task(Worker& w, Node* node) { | ||
| 1718 | + //TF_EXECUTOR_EXCEPTION_HANDLER(w, node, { | ||
| 1719 | + | ||
| 1720 | + if((node->_state.load(std::memory_order_relaxed) & Node::PREEMPTED) == 0) { | ||
| 1709 | 1721 | auto handle = std::get_if<Node::Subflow>(&node->_handle); | |
| 1710 | 1722 | handle->subgraph._clear(); | |
| 1711 | 1723 | Subflow sf(*this, w, node, handle->subgraph); | |
| 1724 | + | ||
| 1725 | + _observer_prologue(w, node); | ||
| 1712 | 1726 | handle->work(sf); | |
| 1713 | - if(sf._joinable) { | ||
| 1714 | - _corun_graph(w, node, handle->subgraph); | ||
| 1727 | + _observer_epilogue(w, node); | ||
| 1728 | + | ||
| 1729 | + // spawn the subflow if it is joinable and its graph is non-empty | ||
| 1730 | + if(sf._joinable && handle->subgraph.size()) { | ||
| 1731 | + | ||
| 1732 | + // signal the executor to preempt this node | ||
| 1733 | + node->_state.fetch_or(Node::PREEMPTED, std::memory_order_relaxed); | ||
| 1734 | + | ||
| 1735 | + // set up and schedule the graph | ||
| 1736 | + auto N = _set_up_graph(handle->subgraph, node, node->_topology, 0); | ||
| 1737 | + node->_join_counter.fetch_add(N, std::memory_order_relaxed); | ||
| 1738 | + _schedule(w, handle->subgraph._nodes, N); | ||
| 1739 | + return true; | ||
| 1715 | 1740 | } | |
| 1716 | - node->_process_exception(); | ||
| 1717 | - }); | ||
| 1718 | - _observer_epilogue(w, node); | ||
| 1741 | + //node->_process_exception(); | ||
| 1742 | + } | ||
| 1743 | + else { | ||
| 1744 | + node->_state.fetch_and(~Node::PREEMPTED, std::memory_order_relaxed); | ||
| 1745 | + } | ||
| 1746 | + | ||
| 1747 | + return false; | ||
| 1719 | 1748 | } | |
| 1720 | 1749 | ||
| 1721 | 1750 | // Procedure: _detach_subflow_task | |
@@ -1726,16 +1755,20 @@ inline void Executor::_detach_subflow_task(Worker& w, Node* p, Graph& g) { | |||
| 1726 | 1755 | return; | |
| 1727 | 1756 | } | |
| 1728 | 1757 | ||
| 1729 | - SmallVector<Node*> src; | ||
| 1730 | - _set_up_graph(g, nullptr, p->_topology, Node::DETACHED, src); | ||
| 1758 | + auto N = _set_up_graph(g, nullptr, p->_topology, Node::DETACHED); | ||
| 1759 | + | ||
| 1760 | + // need to fetch the node to a local vector since the graph will be | ||
| 1761 | + // merged to the parent taskflow with move semantics | ||
| 1762 | + std::vector<Node*> local(N); | ||
| 1763 | + std::copy(g._nodes.begin(), g._nodes.begin() + N, local.begin()); | ||
| 1731 | 1764 | ||
| 1732 | 1765 | { | |
| 1733 | 1766 | std::lock_guard<std::mutex> lock(p->_topology->_taskflow._mutex); | |
| 1734 | 1767 | p->_topology->_taskflow._graph._merge(std::move(g)); | |
| 1735 | 1768 | } | |
| 1736 | - | ||
| 1737 | - p->_topology->_join_counter.fetch_add(src.size(), std::memory_order_relaxed); | ||
| 1738 | - _schedule(w, src); | ||
| 1769 | + | ||
| 1770 | + p->_topology->_join_counter.fetch_add(N, std::memory_order_relaxed); | ||
| 1771 | + _schedule(w, local, N); | ||
| 1739 | 1772 | } | |
| 1740 | 1773 | ||
| 1741 | 1774 | // Procedure: _corun_graph | |
@@ -1748,12 +1781,9 @@ inline void Executor::_corun_graph(Worker& w, Node* p, Graph& g) { | |||
| 1748 | 1781 | return; | |
| 1749 | 1782 | } | |
| 1750 | 1783 | ||
| 1751 | - SmallVector<Node*> src; | ||
| 1752 | - | ||
| 1753 | - _set_up_graph(g, p, p->_topology, 0, src); | ||
| 1754 | - p->_join_counter.fetch_add(src.size(), std::memory_order_relaxed); | ||
| 1755 | - | ||
| 1756 | - _schedule(w, src); | ||
| 1784 | + auto N = _set_up_graph(g, p, p->_topology, 0); | ||
| 1785 | + p->_join_counter.fetch_add(N, std::memory_order_relaxed); | ||
| 1786 | + _schedule(w, g._nodes, N); | ||
| 1757 | 1787 | ||
| 1758 | 1788 | _corun_until(w, [p] () -> bool { | |
| 1759 | 1789 | return p->_join_counter.load(std::memory_order_acquire) == 0; } | |
@@ -1806,15 +1836,19 @@ inline void Executor::_invoke_multi_condition_task( | |||
| 1806 | 1836 | ||
| 1807 | 1837 | // Procedure: _invoke_module_task | |
| 1808 | 1838 | inline bool Executor::_invoke_module_task(Worker& w, Node* node) { | |
| 1839 | + | ||
| 1809 | 1840 | // spawns only non-empty module | |
| 1810 | 1841 | if(auto m = std::get_if<Node::Module>(&node->_handle); m->graph.size()) { | |
| 1842 | + | ||
| 1811 | 1843 | // first entry - not spawned yet | |
| 1812 | 1844 | if((node->_state.load(std::memory_order_relaxed) & Node::PREEMPTED) == 0) { | |
| 1845 | + | ||
| 1846 | + // signal the executor to preempt this node | ||
| 1813 | 1847 | node->_state.fetch_or(Node::PREEMPTED, std::memory_order_relaxed); | |
| 1814 | - SmallVector<Node*> src; | ||
| 1815 | - _set_up_graph(m->graph, node, node->_topology, 0, src); | ||
| 1816 | - node->_join_counter.fetch_add(src.size(), std::memory_order_relaxed); | ||
| 1817 | - _schedule(w, src); | ||
| 1848 | + | ||
| 1849 | + auto N = _set_up_graph(m->graph, node, node->_topology, 0); | ||
| 1850 | + node->_join_counter.fetch_add(N, std::memory_order_relaxed); | ||
| 1851 | + _schedule(w, m->graph._nodes, N); | ||
| 1818 | 1852 | return true; | |
| 1819 | 1853 | } | |
| 1820 | 1854 | // second entry - already spawned | |
@@ -2047,29 +2081,31 @@ inline void Executor::wait_for_all() { | |||
| 2047 | 2081 | inline void Executor::_set_up_topology(Worker* worker, Topology* tpg) { | |
| 2048 | 2082 | ||
| 2049 | 2083 | // ---- under taskflow lock ---- | |
| 2050 | - | ||
| 2051 | - tpg->_sources.clear(); | ||
| 2052 | 2084 | tpg->_taskflow._graph._clear_detached(); | |
| 2053 | - _set_up_graph(tpg->_taskflow._graph, nullptr, tpg, 0, tpg->_sources); | ||
| 2054 | - tpg->_join_counter.store(tpg->_sources.size(), std::memory_order_relaxed); | ||
| 2085 | + tpg->_num_sources = _set_up_graph(tpg->_taskflow._graph, nullptr, tpg, 0); | ||
| 2086 | + tpg->_join_counter.store(tpg->_num_sources, std::memory_order_relaxed); | ||
| 2055 | 2087 | ||
| 2056 | - worker ? _schedule(*worker, tpg->_sources) : _schedule(tpg->_sources); | ||
| 2088 | + worker ? _schedule(*worker, tpg->_taskflow._graph._nodes, tpg->_num_sources) : | ||
| 2089 | + _schedule( tpg->_taskflow._graph._nodes, tpg->_num_sources); | ||
| 2057 | 2090 | } | |
| 2058 | 2091 | ||
| 2059 | 2092 | // Function: _set_up_graph | |
| 2060 | - inline void Executor::_set_up_graph( | ||
| 2061 | - Graph& g, Node* parent, Topology* tpg, int state, SmallVector<Node*>& src | ||
| 2093 | + inline size_t Executor::_set_up_graph( | ||
| 2094 | + Graph& g, Node* parent, Topology* tpg, int state | ||
| 2062 | 2095 | ) { | |
| 2063 | - for(auto node : g._nodes) { | ||
| 2096 | + size_t num_sources = 0; | ||
| 2097 | + for(size_t i=0; i<g._nodes.size(); i++) { | ||
| 2098 | + auto node = g._nodes[i]; | ||
| 2064 | 2099 | node->_topology = tpg; | |
| 2065 | 2100 | node->_parent = parent; | |
| 2066 | 2101 | node->_state.store(state, std::memory_order_relaxed); | |
| 2067 | - if(node->num_dependents() == 0) { | ||
| 2068 | - src.push_back(node); | ||
| 2069 | - } | ||
| 2070 | 2102 | node->_set_up_join_counter(); | |
| 2071 | 2103 | node->_exception_ptr = nullptr; | |
| 2104 | + if(node->num_dependents() == 0) { | ||
| 2105 | + std::swap(g._nodes[num_sources++], g._nodes[i]); | ||
| 2106 | + } | ||
| 2072 | 2107 | } | |
| 2108 | + return num_sources; | ||
| 2073 | 2109 | } | |
| 2074 | 2110 | ||
| 2075 | 2111 | // Function: _tear_down_topology | |
@@ -2083,8 +2119,8 @@ inline void Executor::_tear_down_topology(Worker& worker, Topology* tpg) { | |||
| 2083 | 2119 | if(!tpg->_exception_ptr && !tpg->cancelled() && !tpg->_pred()) { | |
| 2084 | 2120 | //assert(tpg->_join_counter == 0); | |
| 2085 | 2121 | std::lock_guard<std::mutex> lock(f._mutex); | |
| 2086 | - tpg->_join_counter.store(tpg->_sources.size(), std::memory_order_relaxed); | ||
| 2087 | - _schedule(worker, tpg->_sources); | ||
| 2122 | + tpg->_join_counter.store(tpg->_num_sources, std::memory_order_relaxed); | ||
| 2123 | + _schedule(worker, tpg->_taskflow._graph._nodes, tpg->_num_sources); | ||
| 2088 | 2124 | } | |
| 2089 | 2125 | // case 2: the final run of this topology | |
| 2090 | 2126 | else { | |
@@ -2255,9 +2291,9 @@ void Runtime::release(I begin, I end) { | |||
| 2255 | 2291 | ||
| 2256 | 2292 | // Destructor | |
| 2257 | 2293 | inline Runtime::~Runtime() { | |
| 2258 | - _executor._corun_until(_worker, [this] () -> bool { | ||
| 2259 | - return _parent->_join_counter.load(std::memory_order_acquire) == 0; | ||
| 2260 | - }); | ||
| 2294 | + //_executor._corun_until(_worker, [this] () -> bool { | ||
| 2295 | + // return _parent->_join_counter.load(std::memory_order_acquire) == 0; | ||
| 2296 | + //}); | ||
| 2261 | 2297 | } | |
| 2262 | 2298 | ||
| 2263 | 2299 | // ------------------------------------ | |
@@ -2290,18 +2326,6 @@ void Runtime::silent_async(P&& params, F&& f) { | |||
| 2290 | 2326 | _silent_async(*pt::worker, std::forward<P>(params), std::forward<F>(f)); | |
| 2291 | 2327 | } | |
| 2292 | 2328 | ||
| 2293 | - // Function: silent_async_unchecked | ||
| 2294 | - template <typename F> | ||
| 2295 | - void Runtime::silent_async_unchecked(F&& f) { | ||
| 2296 | - _silent_async(_worker, DefaultTaskParams{}, std::forward<F>(f)); | ||
| 2297 | - } | ||
| 2298 | - | ||
| 2299 | - // Function: silent_async_unchecked | ||
| 2300 | - template <typename P, typename F> | ||
| 2301 | - void Runtime::silent_async_unchecked(P&& params, F&& f) { | ||
| 2302 | - _silent_async(_worker, std::forward<P>(params), std::forward<F>(f)); | ||
| 2303 | - } | ||
| 2304 | - | ||
| 2305 | 2329 | // ------------------------------------ | |
| 2306 | 2330 | // Runtime::async series | |
| 2307 | 2331 | // ------------------------------------ | |
| Back | FazBrowse Home | New Git URL |
0 commit comments