#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include
#include
#include
#include
#include
#include
// --------------------------------------------------------
// Testcase: Taskflow.Builder
// --------------------------------------------------------
TEST_CASE("Taskflow.Builder" * doctest::timeout(5)){
size_t num_workers = 4;
size_t num_tasks = 100;
tf::Taskflow tf(static_cast(num_workers));
REQUIRE(tf.num_workers() == num_workers);
std::atomic counter {0};
std::vector silent_tasks;
std::vector>> tasks;
SUBCASE("Placeholder") {
for(size_t i=0; i0){
tasks[i-1].first.precede(tasks[i].first);
}
if(i==0) {
REQUIRE(tasks[i].first.num_dependents() == 0);
}
else {
REQUIRE(tasks[i].first.num_dependents() == 1);
}
}
tf.wait_for_all();
}
SUBCASE("LinearCounter"){
for(size_t i=0;i0){
tf.precede(std::get<0>(tasks[i-1]), std::get<0>(tasks[i]));
}
}
tf.wait_for_all();
REQUIRE(counter == num_tasks);
REQUIRE(tf.num_nodes() == 0);
}
SUBCASE("Broadcast"){
auto src = tf.silent_emplace([&counter]() {counter -= 1;});
for(size_t i=1; i(num_workers));
REQUIRE(tf.num_workers() == num_workers);
std::atomic counter {0};
std::vector silent_tasks;
for(size_t i=0;i(num_workers));
std::vector vec(num_data, 0);
tf.parallel_for(vec, [] (int& v) { v = 64; }, group ? ::rand() : 0);
for(const auto v : vec) {
REQUIRE(v == 0);
}
tf.wait_for_all();
for(const auto v : vec) {
REQUIRE(v == 64);
}
};
const auto reducer = [](size_t num_workers, size_t num_data, bool group){
tf::Taskflow tf(static_cast(num_workers));
std::vector vec(num_data, 0);
std::atomic sum(0);
tf.parallel_for(vec, [&](auto) { ++sum; }, group ? ::rand() : 0);
REQUIRE(sum == 0);
tf.wait_for_all();
REQUIRE(sum == vec.size());
};
// map
SUBCASE("Map") {
for(size_t num_workers=0; num_workers<=4; ++num_workers){
for(size_t num_data=1; num_data<=59049; num_data *= 3){
mapper(num_workers, num_data, true);
mapper(num_workers, num_data, false);
}
}
}
// reduce
SUBCASE("Reduce") {
for(size_t num_workers=0; num_workers<=4; ++num_workers){
for(size_t num_data=1; num_data<=59049; num_data *= 3){
reducer(num_workers, num_data, true);
reducer(num_workers, num_data, false);
}
}
}
}
// --------------------------------------------------------
// Testcase: Taskflow.Reduce
// --------------------------------------------------------
TEST_CASE("Taskflow.Reduce" * doctest::timeout(5)) {
const auto plus_test = [](const size_t num_workers, auto &&data){
tf::Taskflow tf(static_cast(num_workers));
int result {0};
std::iota(data.begin(), data.end(), 1);
tf.reduce(data.begin(), data.end(), result, std::plus());
tf.wait_for_all();
REQUIRE(result == std::accumulate(data.begin(), data.end(), 0, std::plus()));
};
const auto multiply_test = [](const size_t num_workers, auto &&data){
tf::Taskflow tf(static_cast(num_workers));
std::fill(data.begin(), data.end(), 1.0);
double result {2.0};
tf.reduce(data.begin(), data.end(), result, std::multiplies());
tf.wait_for_all();
REQUIRE(result == std::accumulate(data.begin(), data.end(), 2.0, std::multiplies()));
};
const auto max_test = [](const size_t num_workers, auto &&data){
tf::Taskflow tf(static_cast(num_workers));
std::iota(data.begin(), data.end(), 1);
int result {0};
auto lambda = [](const auto& l, const auto& r){return std::max(l, r);};
tf.reduce(data.begin(), data.end(), result, lambda);
tf.wait_for_all();
REQUIRE(result == std::accumulate(data.begin(), data.end(), 0, lambda));
};
const auto min_test = [](const size_t num_workers, auto &&data){
tf::Taskflow tf(static_cast(num_workers));
std::iota(data.begin(), data.end(), 1);
int result {std::numeric_limits::max()};
auto lambda = [](const auto& l, const auto& r){return std::min(l, r);};
tf.reduce(data.begin(), data.end(), result, lambda);
tf.wait_for_all();
REQUIRE(result == std::accumulate(
data.begin(), data.end(), std::numeric_limits::max(), lambda)
);
};
for(size_t i=0; i<=4; ++i){
for(size_t j=0; j<=256; j=j*2+1){
plus_test(i, std::vector(j));
plus_test(i, std::list(j));
multiply_test(i, std::vector(j));
multiply_test(i, std::list(j));
max_test(i, std::vector(j));
max_test(i, std::list(j));
min_test(i, std::vector(j));
min_test(i, std::list(j));
}
}
}
// --------------------------------------------------------
// Testcase: Taskflow.ReduceMin
// --------------------------------------------------------
TEST_CASE("Taskflow.ReduceMin" * doctest::timeout(5)) {
for(int w=0; w<=4; w++) {
tf::Taskflow tf(w);
for(int i=0; i<=65536; i = (i <= 1024) ? i + 1 : i*2 + 1) {
std::vector data(i);
int gold = std::numeric_limits::max();
int test = std::numeric_limits::max();
for(auto& d : data) {
d = ::rand();
gold = std::min(gold, d);
}
tf.reduce_min(data.begin(), data.end(), test);
tf.wait_for_all();
REQUIRE(test == gold);
}
}
}
// --------------------------------------------------------
// Testcase: Taskflow.ReduceMax
// --------------------------------------------------------
TEST_CASE("Taskflow.ReduceMax" * doctest::timeout(5)) {
for(int w=0; w<=4; w++) {
tf::Taskflow tf(w);
for(int i=0; i<=65536; i = (i <= 1024) ? i + 1 : i*2 + 1) {
std::vector data(i);
int gold = std::numeric_limits::min();
int test = std::numeric_limits::min();
for(auto& d : data) {
d = ::rand();
gold = std::max(gold, d);
}
tf.reduce_max(data.begin(), data.end(), test);
tf.wait_for_all();
REQUIRE(test == gold);
}
}
}
// --------------------------------------------------------
// Testcase: Taskflow.JoinedSubflow
// --------------------------------------------------------
TEST_CASE("Taskflow.JoinedSubflow" * doctest::timeout(5)){
using namespace std::literals::chrono_literals;
SUBCASE("Trivial") {
// Empty subflow test
for(unsigned W=0; W<=4; ++W) {
tf::Taskflow tf(W);
// empty flow with future
tf::Task subflow3, subflow3_;
std::future fu3, fu3_;
std::atomic fu3v{0}, fu3v_{0};
// empty flow
auto subflow1 = tf.silent_emplace([&] (auto& fb) {
fu3v++;
}).name("subflow1");
// nested empty flow
auto subflow2 = tf.silent_emplace([&] (auto& fb) {
fu3v++;
fb.silent_emplace([&] (auto& fb) {
fu3v++;
fb.silent_emplace( [&] (auto& fb) {
fu3v++;
}).name("subflow2_1_1");
}).name("subflow2_1");
}).name("subflow2");
std::tie(subflow3, fu3) = tf.emplace([&] (auto& fb) {
REQUIRE(fu3v == 4);
fu3v++;
fu3v_++;
std::tie(subflow3_, fu3_) = fb.emplace([&] (auto& fb) {
REQUIRE(fu3v_ == 3);
fu3v++;
fu3v_++;
return 200;
});
subflow3_.name("subflow3_");
// hereafter we use 100us to avoid dangling reference ...
auto s1 = fb.silent_emplace([&] () {
fu3v_++;
fu3v++;
REQUIRE(fu3.valid());
REQUIRE(fu3.wait_for (100us) != std::future_status::ready);
REQUIRE(fu3_.valid());
REQUIRE(fu3_.wait_for(100us) != std::future_status::ready);
}).name("s1");
auto s2 = fb.silent_emplace([&] () {
fu3v_++;
fu3v++;
REQUIRE(fu3.valid());
REQUIRE(fu3.wait_for (100us) != std::future_status::ready);
REQUIRE(fu3_.valid());
REQUIRE(fu3_.wait_for(100us) != std::future_status::ready);
}).name("s2");
auto s3 = fb.silent_emplace([&] () {
fu3v++;
REQUIRE(fu3v_ == 4);
REQUIRE(fu3.valid());
REQUIRE(fu3.wait_for (100us) != std::future_status::ready);
REQUIRE(fu3_.valid());
REQUIRE(fu3_.wait_for(100us) == std::future_status::ready);
}).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.silent_emplace([&] () {
REQUIRE(fu3v == 9);
REQUIRE(fu3.wait_for(std::chrono::seconds(0)) == std::future_status::ready);
fu3v++;
}).name("subflow4");
subflow1.precede(subflow2);
subflow2.precede(subflow3);
subflow3.precede(subflow4);
tf.dispatch().get();
REQUIRE(fu3v == 10);
REQUIRE(fu3v_ == 4);
REQUIRE(fu3.get() == 100);
REQUIRE(fu3_.get() == 200);
} // End of for loop
}
// Mixed intra- and inter- operations
SUBCASE("Complex") {
for(unsigned W=0; W<=4; ++W) {
tf::Taskflow tf(W);
std::vector data;
int sum {0};
auto A = tf.silent_emplace([&data] () {
for(int i=0; i<10; ++i) {
data.push_back(1);
}
});
std::atomic count = 0;
auto B = tf.silent_emplace([&count, &data, &sum](auto& fb){
auto [src, tgt] = fb.reduce(data.begin(), data.end(), sum, std::plus());
fb.silent_emplace([&sum] () { REQUIRE(sum == 0); }).precede(src);
tgt.precede(fb.silent_emplace([&sum] () { REQUIRE(sum == 10); }));
for(size_t i=0; i<10; i ++){
++count;
}
auto n = fb.silent_emplace([&count](auto& fb){
REQUIRE(count == 20);
++count;
auto prev = fb.silent_emplace([&count](){
REQUIRE(count == 21);
++count;
});
for(size_t i=0; i<10; i++){
auto next = fb.silent_emplace([&count, i](){
REQUIRE(count == 22+i);
++count;
});
prev.precede(next);
prev = next;
}
});
for(size_t i=0; i<10; i++){
fb.silent_emplace([&count](){ ++count; }).precede(n);
}
});
A.precede(B);
tf.wait_for_all();
REQUIRE(count == 32);
REQUIRE(sum == 10);
}
}
}
// --------------------------------------------------------
// Testcase: Taskflow.DetachedSubflow
// --------------------------------------------------------
TEST_CASE("Taskflow.DetachedSubflow" * doctest::timeout(5)) {
using namespace std::literals::chrono_literals;
SUBCASE("Trivial") {
// Empty subflow test
for(unsigned W=0; W<=4; ++W) {
tf::Taskflow tf(W);
// empty flow with future
tf::Task subflow3, subflow3_;
std::future fu3, fu3_;
std::atomic fu3v{0}, fu3v_{0};
// empty flow
auto subflow1 = tf.silent_emplace([&] (auto& fb) {
fu3v++;
fb.detach();
}).name("subflow1");
// nested empty flow
auto subflow2 = tf.silent_emplace([&] (auto& fb) {
fu3v++;
fb.silent_emplace([&] (auto& fb) {
fu3v++;
fb.silent_emplace( [&] (auto& fb) {
fu3v++;
}).name("subflow2_1_1");
fb.detach();
}).name("subflow2_1");
fb.detach();
}).name("subflow2");
std::tie(subflow3, fu3) = tf.emplace([&] (auto& fb) {
REQUIRE((fu3v >= 2 && fu3v <= 4));
fu3v++;
fu3v_++;
std::tie(subflow3_, fu3_) = fb.emplace([&] (auto& fb) {
REQUIRE(fu3v_ == 3);
fu3v++;
fu3v_++;
return 200;
});
subflow3_.name("subflow3_");
// hereafter we use 100us to avoid dangling reference ...
auto s1 = fb.silent_emplace([&] () {
fu3v_++;
fu3v++;
REQUIRE(fu3.valid());
REQUIRE(fu3.wait_for (100us) == std::future_status::ready);
REQUIRE(fu3_.valid());
REQUIRE(fu3_.wait_for(100us) != std::future_status::ready);
}).name("s1");
auto s2 = fb.silent_emplace([&] () {
fu3v_++;
fu3v++;
REQUIRE(fu3.valid());
REQUIRE(fu3.wait_for (100us) == std::future_status::ready);
REQUIRE(fu3_.valid());
REQUIRE(fu3_.wait_for(100us) != std::future_status::ready);
}).name("s2");
auto s3 = fb.silent_emplace([&] () {
fu3v++;
REQUIRE(fu3v_ == 4);
REQUIRE(fu3.valid());
REQUIRE(fu3.wait_for (100us) == std::future_status::ready);
REQUIRE(fu3_.valid());
REQUIRE(fu3_.wait_for(100us) == std::future_status::ready);
}).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.silent_emplace([&] () {
REQUIRE((fu3v >= 3 && fu3v <= 9));
REQUIRE(fu3.wait_for(std::chrono::seconds(0)) == std::future_status::ready);
fu3v++;
}).name("subflow4");
subflow1.precede(subflow2);
subflow2.precede(subflow3);
subflow3.precede(subflow4);
tf.dispatch().get();
REQUIRE(fu3v == 10);
REQUIRE(fu3v_ == 4);
REQUIRE(fu3.get() == 100);
REQUIRE(fu3_.get() == 200);
}
}
}
/*// --------------------------------------------------------
// Testcase: Taskflow.ParallelRange
// --------------------------------------------------------
TEST_CASE("Taskflow.ParallelRange" * doctest::timeout(5)) {
using namespace std::chrono_literals;
const auto mapper = [](size_t num_workers, size_t num_data, bool group){
tf::Taskflow tf(num_workers);
std::vector vec(num_data, 0);
tf.parallel_range(0ul, num_data, [&] (size_t i) { vec[i] = 64; }, group ? ::rand() : 0);
for(const auto v : vec) {
REQUIRE(v == 0);
}
tf.wait_for_all();
for(const auto v : vec) {
REQUIRE(v == 64);
}
};
const auto reducer = [](size_t num_workers, size_t num_data, bool group){
tf::Taskflow tf(num_workers);
std::atomic sum(0);
tf.parallel_range(0ul, num_data, [&](size_t i) { sum += i; }, group ? ::rand() : 0);
REQUIRE(sum == 0);
tf.wait_for_all();
REQUIRE(sum == (num_data-1)*num_data/2);
};
// map
SUBCASE("Map") {
for(size_t num_workers=0; num_workers<=4; ++num_workers){
for(size_t num_data=1; num_data<=59049; num_data *= 3){
mapper(num_workers, num_data, true);
mapper(num_workers, num_data, false);
}
}
}
// reduce
SUBCASE("Reduce") {
for(size_t num_workers=0; num_workers<=4; ++num_workers){
for(size_t num_data=1; num_data<=59049; num_data *= 3){
reducer(num_workers, num_data, true);
reducer(num_workers, num_data, false);
}
}
}
}*/