#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #include #include // ---------------------------------------------------------------------------- // Parallel Transform 1 // ---------------------------------------------------------------------------- template void parallel_transform(size_t W) { std::srand(static_cast(time(NULL))); tf::Taskflow taskflow; tf::Executor executor(W); for(size_t N=0; N<1000; N=(N+1)*2) { for(size_t c : {0, 1, 3, 7, 99}) { taskflow.clear(); typename T::const_iterator src_beg; typename T::const_iterator src_end; std::list::iterator tgt_beg; T src; std::list tgt; taskflow.clear(); auto from = taskflow.emplace([&](){ src.resize(N); for(auto& d : src) { d = ::rand() % 10; tgt.emplace_back("hi"); } src_beg = src.begin(); src_end = src.end(); tgt_beg = tgt.begin(); }); auto to = taskflow.transform( std::ref(src_beg), std::ref(src_end), std::ref(tgt_beg), [] (const auto& in) { return std::to_string(in+10); }, P(c) ); from.precede(to); executor.run(taskflow).wait(); auto s_itr = src.begin(); auto d_itr = tgt.begin(); while(s_itr != src.end()) { REQUIRE(*d_itr++ == std::to_string(*s_itr++ + 10)); } } } } // guided TEST_CASE("ParallelTransform.Guided.1thread") { parallel_transform, tf::GuidedPartitioner<>>(1); parallel_transform, tf::GuidedPartitioner<>>(1); } TEST_CASE("ParallelTransform.Guided.2threads") { parallel_transform, tf::GuidedPartitioner<>>(2); parallel_transform, tf::GuidedPartitioner<>>(2); } TEST_CASE("ParallelTransform.Guided.3threads") { parallel_transform, tf::GuidedPartitioner<>>(3); parallel_transform, tf::GuidedPartitioner<>>(3); } TEST_CASE("ParallelTransform.Guided.4threads") { parallel_transform, tf::GuidedPartitioner<>>(4); parallel_transform, tf::GuidedPartitioner<>>(4); } // random TEST_CASE("ParallelTransform.Random.1thread") { parallel_transform, tf::RandomPartitioner<>>(1); parallel_transform, tf::RandomPartitioner<>>(1); } TEST_CASE("ParallelTransform.Random.2threads") { parallel_transform, tf::RandomPartitioner<>>(2); parallel_transform, tf::RandomPartitioner<>>(2); } TEST_CASE("ParallelTransform.Random.3threads") { parallel_transform, tf::RandomPartitioner<>>(3); parallel_transform, tf::RandomPartitioner<>>(3); } TEST_CASE("ParallelTransform.Random.4threads") { parallel_transform, tf::RandomPartitioner<>>(4); parallel_transform, tf::RandomPartitioner<>>(4); } // static TEST_CASE("ParallelTransform.Static.1thread") { parallel_transform, tf::StaticPartitioner<>>(1); parallel_transform, tf::StaticPartitioner<>>(1); } TEST_CASE("ParallelTransform.Static.2threads") { parallel_transform, tf::StaticPartitioner<>>(2); parallel_transform, tf::StaticPartitioner<>>(2); } TEST_CASE("ParallelTransform.Static.3threads") { parallel_transform, tf::StaticPartitioner<>>(3); parallel_transform, tf::StaticPartitioner<>>(3); } TEST_CASE("ParallelTransform.Static.4threads") { parallel_transform, tf::StaticPartitioner<>>(4); parallel_transform, tf::StaticPartitioner<>>(4); } // ---------------------------------------------------------------------------- // Parallel Transform 2 // ---------------------------------------------------------------------------- template void parallel_transform2(size_t W) { std::srand(static_cast(time(NULL))); tf::Taskflow taskflow; tf::Executor executor(W); for(size_t N=0; N<1000; N=(N+1)*2) { for(size_t c : {0, 1, 3, 7, 99}) { taskflow.clear(); typename T::const_iterator src_beg; typename T::const_iterator src_end; std::list::iterator tgt_beg; T src; std::list tgt; taskflow.clear(); auto from = taskflow.emplace([&](){ src.resize(N); for(auto& d : src) { d = ::rand() % 10; tgt.emplace_back("hi"); } src_beg = src.begin(); src_end = src.end(); tgt_beg = tgt.begin(); }); auto to = taskflow.transform( std::ref(src_beg), std::ref(src_end), std::ref(src_beg), std::ref(tgt_beg), [] (const auto& in1, const auto& in2) { return std::to_string(in1 + in2 + 10); }, P(c) ); from.precede(to); executor.run(taskflow).wait(); auto s_itr = src.begin(); auto d_itr = tgt.begin(); while(s_itr != src.end()) { REQUIRE(*d_itr++ == std::to_string(2 * *s_itr++ + 10)); } } } } // guided TEST_CASE("ParallelTransform2.Guided.1thread") { parallel_transform2, tf::GuidedPartitioner<>>(1); parallel_transform2, tf::GuidedPartitioner<>>(1); } TEST_CASE("ParallelTransform2.Guided.2threads") { parallel_transform2, tf::GuidedPartitioner<>>(2); parallel_transform2, tf::GuidedPartitioner<>>(2); } TEST_CASE("ParallelTransform2.Guided.3threads") { parallel_transform2, tf::GuidedPartitioner<>>(3); parallel_transform2, tf::GuidedPartitioner<>>(3); } TEST_CASE("ParallelTransform2.Guided.4threads") { parallel_transform2, tf::GuidedPartitioner<>>(4); parallel_transform2, tf::GuidedPartitioner<>>(4); } // dynamic TEST_CASE("ParallelTransform2.Dynamic.1thread") { parallel_transform2, tf::DynamicPartitioner<>>(1); parallel_transform2, tf::DynamicPartitioner<>>(1); } TEST_CASE("ParallelTransform2.Dynamic.2threads") { parallel_transform2, tf::DynamicPartitioner<>>(2); parallel_transform2, tf::DynamicPartitioner<>>(2); } TEST_CASE("ParallelTransform2.Dynamic.3threads") { parallel_transform2, tf::DynamicPartitioner<>>(3); parallel_transform2, tf::DynamicPartitioner<>>(3); } TEST_CASE("ParallelTransform2.Dynamic.4threads") { parallel_transform2, tf::DynamicPartitioner<>>(4); parallel_transform2, tf::DynamicPartitioner<>>(4); } // static TEST_CASE("ParallelTransform2.Static.1thread") { parallel_transform2, tf::StaticPartitioner<>>(1); parallel_transform2, tf::StaticPartitioner<>>(1); } TEST_CASE("ParallelTransform2.Static.2threads") { parallel_transform2, tf::StaticPartitioner<>>(2); parallel_transform2, tf::StaticPartitioner<>>(2); } TEST_CASE("ParallelTransform2.Static.3threads") { parallel_transform2, tf::StaticPartitioner<>>(3); parallel_transform2, tf::StaticPartitioner<>>(3); } TEST_CASE("ParallelTransform2.Static.4threads") { parallel_transform2, tf::StaticPartitioner<>>(4); parallel_transform2, tf::StaticPartitioner<>>(4); } // random TEST_CASE("ParallelTransform2.Random.1thread") { parallel_transform2, tf::RandomPartitioner<>>(1); parallel_transform2, tf::RandomPartitioner<>>(1); } TEST_CASE("ParallelTransform2.Random.2threads") { parallel_transform2, tf::RandomPartitioner<>>(2); parallel_transform2, tf::RandomPartitioner<>>(2); } TEST_CASE("ParallelTransform2.Random.3threads") { parallel_transform2, tf::RandomPartitioner<>>(3); parallel_transform2, tf::RandomPartitioner<>>(3); } TEST_CASE("ParallelTransform2.Random.4threads") { parallel_transform2, tf::RandomPartitioner<>>(4); parallel_transform2, tf::RandomPartitioner<>>(4); } // ---------------------------------------------------------------------------- // Parallel Transform 3 // ---------------------------------------------------------------------------- template void parallel_transform3(size_t W) { std::srand(static_cast(time(NULL))); tf::Taskflow taskflow; tf::Executor executor(W); using std::string; using std::size_t; for(size_t N=0; N<1000; N=(N+1)*2) { std::multimap src; /** Reference implementation with std::transform */ std::vector ref; /** Target implementation with Subflow::transform */ std::vector tgt; typename std::vector::iterator tgt_beg; /** A generic function to cast integers to string */ const auto myFunction = [](const size_t x) -> string { return "id_" + std::to_string(x); }; taskflow.clear(); /** Group integers 0..(N-1) into ten groups, * each having an unique key `d`. */ auto from = taskflow.emplace([&, N](){ for(size_t i = 0; i < N; i++) { const int d = ::rand() % 10; src.emplace(d, i); } ref.resize(N); tgt.resize(N); tgt_beg = tgt.begin(); }); auto to_ref = taskflow.emplace([&]() { // Find entries matching key = 0. // This can return empty results. const auto [src_beg, src_end] = src.equal_range(0); const size_t n_matching = std::distance(src_beg, src_end); ref.resize(n_matching); // Extract all values having matching key value. std::transform(src_beg, src_end, ref.begin(), [&](const auto& x) -> string { return myFunction(x.second); } ); }); /** Dynamic scheduling with Subflow::transform */ auto to_tgt = taskflow.emplace([&](tf::Subflow& subflow) { // Find entries matching key = 0 const auto [src_beg, src_end] = src.equal_range(0); const size_t n_matching = std::distance(src_beg, src_end); tgt.resize(n_matching); subflow.transform( std::ref(src_beg), std::ref(src_end), std::ref(tgt_beg), [&] (const auto& x) -> string { return myFunction(x.second); }, P()); subflow.join(); }); from.precede(to_ref); from.precede(to_tgt); executor.run(taskflow).wait(); /** Target entries much match. */ REQUIRE(std::equal(tgt.begin(), tgt.end(), ref.begin())); } } // guided TEST_CASE("ParallelTransform3.Guided.1thread") { parallel_transform3>(1); parallel_transform3>(1); } TEST_CASE("ParallelTransform3.Guided.2threads") { parallel_transform3>(2); parallel_transform3>(2); } TEST_CASE("ParallelTransform3.Guided.3threads") { parallel_transform3>(3); parallel_transform3>(3); } TEST_CASE("ParallelTransform3.Guided.4threads") { parallel_transform3>(4); parallel_transform3>(4); } // dynamic TEST_CASE("ParallelTransform3.Dynamic.1thread") { parallel_transform3>(1); parallel_transform3>(1); } TEST_CASE("ParallelTransform3.Dynamic.2threads") { parallel_transform3>(2); parallel_transform3>(2); } TEST_CASE("ParallelTransform3.Dynamic.3threads") { parallel_transform3>(3); parallel_transform3>(3); } TEST_CASE("ParallelTransform3.Dynamic.4threads") { parallel_transform3>(4); parallel_transform3>(4); } // static TEST_CASE("ParallelTransform3.Static.1thread") { parallel_transform3>(1); parallel_transform3>(1); } TEST_CASE("ParallelTransform3.Static.2threads") { parallel_transform3>(2); parallel_transform3>(2); } TEST_CASE("ParallelTransform3.Static.3threads") { parallel_transform3>(3); parallel_transform3>(3); } TEST_CASE("ParallelTransform3.Static.4threads") { parallel_transform3>(4); parallel_transform3>(4); } // random TEST_CASE("ParallelTransform3.Random.1thread") { parallel_transform3>(1); parallel_transform3>(1); } TEST_CASE("ParallelTransform3.Random.2threads") { parallel_transform3>(2); parallel_transform3>(2); } TEST_CASE("ParallelTransform3.Random.3threads") { parallel_transform3>(3); parallel_transform3>(3); } TEST_CASE("ParallelTransform3.Random.4threads") { parallel_transform3>(4); parallel_transform3>(4); } // ---------------------------------------------------------------------------- // Closure Wrapper // ---------------------------------------------------------------------------- int& GetThreadSpecificContext() { thread_local int context = 0; return context; } const int UPPER = 1000; TEST_CASE("ClosureWrapper.transform.Static" * doctest::timeout(300)) { // Write a test case for using the taskwrapper on tf::transform for (int tc = 1; tc < 16; tc++) { tf::Executor executor(tc); std::atomic wrapper_called_count = 0; tf::Taskflow taskflow; std::vector range(UPPER, 0); std::vector result(UPPER); taskflow.transform(range.begin(), range.end(), begin(result), [&](int) { return GetThreadSpecificContext(); }, tf::StaticPartitioner(1, [&](auto&& task){ wrapper_called_count++; GetThreadSpecificContext() = tc; task(); GetThreadSpecificContext() = 0; }) ); executor.run(taskflow).wait(); REQUIRE(wrapper_called_count == tc); REQUIRE(result == std::vector(UPPER, tc)); } } // Implement for dynamic case for transform TEST_CASE("ClosureWrapper.transform.Dynamic" * doctest::timeout(300)) { for (int tc = 1; tc < 16; tc++) { tf::Executor executor(tc); std::atomic wrapper_called_count = 0; tf::Taskflow taskflow; std::vector range(UPPER); std::iota(range.begin(), range.end(), 0); std::vector result(UPPER); taskflow.transform(range.begin(), range.end(), begin(result), [&](int){ return GetThreadSpecificContext(); }, tf::DynamicPartitioner(1, [&](auto&& task) { wrapper_called_count++; GetThreadSpecificContext() = tc; task(); GetThreadSpecificContext() = 0; }) ); executor.run(taskflow).wait(); REQUIRE(wrapper_called_count <= tc); REQUIRE(result == std::vector(UPPER, tc)); } } //// ---------------------------------------------------------------------------- //// ParallelTransform Exception //// ---------------------------------------------------------------------------- // //void parallel_transform_exception(unsigned W) { // tf::Taskflow taskflow; // tf::Executor executor(W); // // std::vector src(1000000, 0); // std::vector tgt(1000000, 0); // // taskflow.transform(src.begin(), src.end(), tgt.begin(), [](int&) { // throw std::runtime_error("x"); // return 1; // }); // REQUIRE_THROWS_WITH_AS(executor.run(taskflow).get(), "x", std::runtime_error); //} // //TEST_CASE("ParallelTransform.Exception.1thread") { // parallel_transform_exception(1); //} // //TEST_CASE("ParallelTransform.Exception.2threads") { // parallel_transform_exception(2); //} // //TEST_CASE("ParallelTransform.Exception.3threads") { // parallel_transform_exception(3); //} // //TEST_CASE("ParallelTransform.Exception.4threads") { // parallel_transform_exception(4); //}