#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #include #include // -------------------------------------------------------- // Testcase: JoinedSubflow // -------------------------------------------------------- void joined_subflow(unsigned W) { using namespace std::literals::chrono_literals; SUBCASE("Trivial") { tf::Executor executor(W); tf::Taskflow tf; // empty flow with future tf::Task subflow3, subflow3_; //std::future fu3, fu3_; std::atomic fu3v{0}, fu3v_{0}; // empty flow auto subflow1 = tf.emplace([&] (tf::Subflow& fb) { fu3v++; fb.join(); }).name("subflow1"); // nested empty flow auto subflow2 = tf.emplace([&] (tf::Subflow& fb) { fu3v++; fb.emplace([&] (tf::Subflow& fb2) { fu3v++; fb2.emplace( [&] (tf::Subflow& fb3) { fu3v++; fb3.join(); }).name("subflow2_1_1"); }).name("subflow2_1"); }).name("subflow2"); subflow3 = tf.emplace([&] (tf::Subflow& fb) { REQUIRE(fu3v == 4); fu3v++; fu3v_++; subflow3_ = fb.emplace([&] (tf::Subflow& fb2) { REQUIRE(fu3v_ == 3); fu3v++; fu3v_++; //return 200; fb2.join(); }); subflow3_.name("subflow3_"); // hereafter we use 100us to avoid dangling reference ... auto s1 = fb.emplace([&] () { fu3v_++; fu3v++; }).name("s1"); auto s2 = fb.emplace([&] () { fu3v_++; fu3v++; }).name("s2"); auto s3 = fb.emplace([&] () { fu3v++; REQUIRE(fu3v_ == 4); }).name("s3"); s1.precede(subflow3_); s2.precede(subflow3_); subflow3_.precede(s3); REQUIRE(fu3v_ == 1); //return 100; }); subflow3.name("subflow3"); // empty flow to test future auto subflow4 = tf.emplace([&] () { fu3v++; }).name("subflow4"); subflow1.precede(subflow2); subflow2.precede(subflow3); subflow3.precede(subflow4); executor.run(tf).get(); // End of for loop } // Mixed intra- and inter- operations SUBCASE("Complex") { tf::Executor executor(W); tf::Taskflow tf; std::vector data; int sum {0}; auto A = tf.emplace([&data] () { for(int i=0; i<10; ++i) { data.push_back(1); } }); std::atomic count {0}; auto B = tf.emplace([&count, &data, &sum](tf::Subflow& fb){ //auto [src, tgt] = fb.reduce(data.begin(), data.end(), sum, std::plus()); auto task = fb.reduce(data.begin(), data.end(), sum, std::plus()); fb.emplace([&sum] () { REQUIRE(sum == 0); }).precede(task); task.precede(fb.emplace([&sum] () { REQUIRE(sum == 10); })); for(size_t i=0; i<10; i ++){ ++count; } auto n = fb.emplace([&count](tf::Subflow& fb2){ REQUIRE(count == 20); ++count; auto prev = fb2.emplace([&count](){ REQUIRE(count == 21); ++count; }); for(size_t i=0; i<10; i++){ auto next = fb2.emplace([&count, i](){ REQUIRE(count == 22+i); ++count; }); prev.precede(next); prev = next; } }); for(size_t i=0; i<10; i++){ fb.emplace([&count](){ ++count; }).precede(n); } }); A.precede(B); executor.run(tf).get(); REQUIRE(count == 32); REQUIRE(sum == 10); } } TEST_CASE("JoinedSubflow.1thread" * doctest::timeout(300)){ joined_subflow(1); } TEST_CASE("JoinedSubflow.2threads" * doctest::timeout(300)){ joined_subflow(2); } TEST_CASE("JoinedSubflow.3threads" * doctest::timeout(300)){ joined_subflow(3); } TEST_CASE("JoinedSubflow.4threads" * doctest::timeout(300)){ joined_subflow(4); } TEST_CASE("JoinedSubflow.5threads" * doctest::timeout(300)){ joined_subflow(5); } TEST_CASE("JoinedSubflow.6threads" * doctest::timeout(300)){ joined_subflow(6); } TEST_CASE("JoinedSubflow.7threads" * doctest::timeout(300)){ joined_subflow(7); } TEST_CASE("JoinedSubflow.8threads" * doctest::timeout(300)){ joined_subflow(8); } // -------------------------------------------------------- // Testcase: DetachedSubflow // -------------------------------------------------------- void detached_subflow(unsigned W) { using namespace std::literals::chrono_literals; SUBCASE("Trivial") { tf::Executor executor(W); tf::Taskflow tf; // empty flow with future tf::Task subflow3, subflow3_; std::atomic fu3v{0}, fu3v_{0}; // empty flow auto subflow1 = tf.emplace([&] (tf::Subflow& fb) { fu3v++; fb.detach(); }).name("subflow1"); // nested empty flow auto subflow2 = tf.emplace([&] (tf::Subflow& fb) { fu3v++; fb.emplace([&] (tf::Subflow& fb2) { fu3v++; fb2.emplace( [&] (tf::Subflow& fb3) { fu3v++; fb3.join(); }).name("subflow2_1_1"); fb2.detach(); }).name("subflow2_1"); fb.detach(); }).name("subflow2"); subflow3 = tf.emplace([&] (tf::Subflow& fb) { REQUIRE((fu3v >= 2 && fu3v <= 4)); fu3v++; fu3v_++; subflow3_ = fb.emplace([&] (tf::Subflow& fb2) { REQUIRE(fu3v_ == 3); fu3v++; fu3v_++; fb2.join(); }); subflow3_.name("subflow3_"); // hereafter we use 100us to avoid dangling reference ... auto s1 = fb.emplace([&] () { fu3v_++; fu3v++; }).name("s1"); auto s2 = fb.emplace([&] () { fu3v_++; fu3v++; }).name("s2"); auto s3 = fb.emplace([&] () { fu3v++; REQUIRE(fu3v_ == 4); }).name("s3"); s1.precede(subflow3_); s2.precede(subflow3_); subflow3_.precede(s3); REQUIRE(fu3v_ == 1); fb.detach(); //return 100; }); subflow3.name("subflow3"); // empty flow to test future auto subflow4 = tf.emplace([&] () { REQUIRE((fu3v >= 3 && fu3v <= 9)); fu3v++; }).name("subflow4"); subflow1.precede(subflow2); subflow2.precede(subflow3); subflow3.precede(subflow4); executor.run(tf).get(); REQUIRE(fu3v == 10); REQUIRE(fu3v_ == 4); } } TEST_CASE("DetachedSubflow.1thread" * doctest::timeout(300)) { detached_subflow(1); } TEST_CASE("DetachedSubflow.2threads" * doctest::timeout(300)) { detached_subflow(2); } TEST_CASE("DetachedSubflow.3threads" * doctest::timeout(300)) { detached_subflow(3); } TEST_CASE("DetachedSubflow.4threads" * doctest::timeout(300)) { detached_subflow(4); } TEST_CASE("DetachedSubflow.5threads" * doctest::timeout(300)) { detached_subflow(5); } TEST_CASE("DetachedSubflow.6threads" * doctest::timeout(300)) { detached_subflow(6); } TEST_CASE("DetachedSubflow.7threads" * doctest::timeout(300)) { detached_subflow(7); } TEST_CASE("DetachedSubflow.8threads" * doctest::timeout(300)) { detached_subflow(8); } // -------------------------------------------------------- // Testcase: TreeSubflow // -------------------------------------------------------- void detach_spawn(const int max_depth, std::atomic& counter, int depth, tf::Subflow& subflow) { if(depth < max_depth) { counter.fetch_add(1, std::memory_order_relaxed); subflow.emplace([&, max_depth, depth=depth+1](tf::Subflow& sfl){ detach_spawn(max_depth, counter, depth, sfl); } ); subflow.emplace([&, max_depth, depth=depth+1](tf::Subflow& sfr){ detach_spawn(max_depth, counter, depth, sfr); } ); subflow.detach(); } } void join_spawn(const int max_depth, std::atomic& counter, int depth, tf::Subflow& subflow) { if(depth < max_depth) { counter.fetch_add(1, std::memory_order_relaxed); subflow.emplace([&, max_depth, depth=depth+1](tf::Subflow& sfl){ join_spawn(max_depth, counter, depth, sfl); } ); subflow.emplace([&, max_depth, depth=depth+1](tf::Subflow& sfr){ join_spawn(max_depth, counter, depth, sfr); } ); } } void mix_spawn( const int max_depth, std::atomic& counter, int depth, tf::Subflow& subflow ) { if(depth < max_depth) { auto ret = counter.fetch_add(1, std::memory_order_relaxed); subflow.emplace([&, max_depth, depth=depth+1](tf::Subflow& sfl){ mix_spawn(max_depth, counter, depth, sfl); } ).name(std::string("left") + std::to_string(ret%2)); subflow.emplace([&, max_depth, depth=depth+1](tf::Subflow& sfr){ mix_spawn(max_depth, counter, depth, sfr); } ).name(std::string("right") + std::to_string(ret%2)); if(ret % 2) { subflow.detach(); } } } TEST_CASE("TreeSubflow" * doctest::timeout(300)) { SUBCASE("AllDetach") { constexpr int max_depth {10}; for(int W=1; W<=4; W++) { std::atomic counter {0}; tf::Taskflow tf; tf.emplace([&](tf::Subflow& subflow){ detach_spawn(max_depth, counter, 0, subflow); }); tf::Executor executor(W); executor.run(tf).get(); REQUIRE(counter == (1< counter {0}; tf::Taskflow tf; tf.emplace([&](tf::Subflow& subflow){ join_spawn(max_depth, counter, 0, subflow); }); tf::Executor executor(W); executor.run(tf).get(); REQUIRE(counter == (1< counter {0}; tf::Taskflow tf; tf.emplace([&](tf::Subflow& subflow){ mix_spawn(max_depth, counter, 0, subflow); }).name("top task"); tf::Executor executor(W); executor.run(tf).get(); REQUIRE(counter == (1<