1146 lines
26 KiB
C++
1146 lines
26 KiB
C++
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
|
|
|
#include <doctest.h>
|
|
#include <taskflow/taskflow.hpp>
|
|
#include <taskflow/algorithm/for_each.hpp>
|
|
#include <taskflow/algorithm/reduce.hpp>
|
|
|
|
// --------------------------------------------------------
|
|
// Testcase: Type
|
|
// --------------------------------------------------------
|
|
TEST_CASE("Type" * doctest::timeout(300)) {
|
|
|
|
tf::Taskflow taskflow, taskflow2;
|
|
|
|
auto t1 = taskflow.emplace([](){});
|
|
auto t2 = taskflow.emplace([](){ return 1; });
|
|
auto t3 = taskflow.emplace([](tf::Subflow&){ });
|
|
auto t4 = taskflow.composed_of(taskflow2);
|
|
auto t5 = taskflow.emplace([](){ return tf::SmallVector{1, 2}; });
|
|
auto t6 = taskflow.emplace([](tf::Runtime&){});
|
|
auto t7 = taskflow.emplace([](tf::Runtime&){ return 1; });
|
|
auto t8 = taskflow.emplace([](tf::Runtime&){ return tf::SmallVector{1, 2}; });
|
|
|
|
REQUIRE(t1.type() == tf::TaskType::STATIC);
|
|
REQUIRE(t2.type() == tf::TaskType::CONDITION);
|
|
REQUIRE(t3.type() == tf::TaskType::SUBFLOW);
|
|
REQUIRE(t4.type() == tf::TaskType::MODULE);
|
|
REQUIRE(t5.type() == tf::TaskType::CONDITION);
|
|
REQUIRE(t6.type() == tf::TaskType::STATIC);
|
|
REQUIRE(t7.type() == tf::TaskType::CONDITION);
|
|
REQUIRE(t8.type() == tf::TaskType::CONDITION);
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// Testcase: Builder
|
|
// --------------------------------------------------------
|
|
TEST_CASE("Builder" * doctest::timeout(300)) {
|
|
|
|
SUBCASE("EmptyFlow") {
|
|
for(unsigned W=1; W<32; ++W) {
|
|
tf::Executor executor(W);
|
|
tf::Taskflow taskflow;
|
|
REQUIRE(taskflow.num_tasks() == 0);
|
|
REQUIRE(taskflow.empty() == true);
|
|
executor.run(taskflow).wait();
|
|
}
|
|
}
|
|
|
|
const size_t num_tasks = 100;
|
|
|
|
tf::Taskflow taskflow;
|
|
tf::Executor executor;
|
|
|
|
std::atomic<int> counter {0};
|
|
std::vector<tf::Task> silent_tasks;
|
|
std::vector<tf::Task> tasks;
|
|
|
|
SUBCASE("Placeholder") {
|
|
|
|
for(size_t i=0; i<num_tasks; ++i) {
|
|
silent_tasks.emplace_back(taskflow.placeholder().name(std::to_string(i)));
|
|
}
|
|
|
|
for(size_t i=0; i<num_tasks; ++i) {
|
|
REQUIRE(silent_tasks[i].name() == std::to_string(i));
|
|
REQUIRE(silent_tasks[i].num_dependents() == 0);
|
|
REQUIRE(silent_tasks[i].num_successors() == 0);
|
|
}
|
|
|
|
for(auto& task : silent_tasks) {
|
|
task.work([&counter](){ counter++; });
|
|
}
|
|
|
|
executor.run(taskflow).get();
|
|
REQUIRE(counter == num_tasks);
|
|
}
|
|
|
|
SUBCASE("EmbarrassinglyParallel"){
|
|
|
|
for(size_t i=0;i<num_tasks;i++) {
|
|
tasks.emplace_back(taskflow.emplace([&counter]() {counter += 1;}));
|
|
}
|
|
|
|
REQUIRE(taskflow.num_tasks() == num_tasks);
|
|
executor.run(taskflow).get();
|
|
REQUIRE(counter == num_tasks);
|
|
REQUIRE(taskflow.num_tasks() == 100);
|
|
|
|
counter = 0;
|
|
|
|
for(size_t i=0;i<num_tasks;i++){
|
|
silent_tasks.emplace_back(taskflow.emplace([&counter]() {counter += 1;}));
|
|
}
|
|
|
|
REQUIRE(taskflow.num_tasks() == num_tasks * 2);
|
|
executor.run(taskflow).get();
|
|
REQUIRE(counter == num_tasks * 2);
|
|
REQUIRE(taskflow.num_tasks() == 200);
|
|
}
|
|
|
|
SUBCASE("BinarySequence"){
|
|
for(size_t i=0;i<num_tasks;i++){
|
|
if(i%2 == 0){
|
|
tasks.emplace_back(
|
|
taskflow.emplace([&counter]() { REQUIRE(counter == 0); counter += 1;})
|
|
);
|
|
}
|
|
else{
|
|
tasks.emplace_back(
|
|
taskflow.emplace([&counter]() { REQUIRE(counter == 1); counter -= 1;})
|
|
);
|
|
}
|
|
if(i>0){
|
|
//tasks[i-1].first.precede(tasks[i].first);
|
|
tasks[i-1].precede(tasks[i]);
|
|
}
|
|
|
|
if(i==0) {
|
|
//REQUIRE(tasks[i].first.num_dependents() == 0);
|
|
REQUIRE(tasks[i].num_dependents() == 0);
|
|
}
|
|
else {
|
|
//REQUIRE(tasks[i].first.num_dependents() == 1);
|
|
REQUIRE(tasks[i].num_dependents() == 1);
|
|
}
|
|
}
|
|
executor.run(taskflow).get();
|
|
}
|
|
|
|
SUBCASE("LinearCounter"){
|
|
for(size_t i=0;i<num_tasks;i++){
|
|
tasks.emplace_back(
|
|
taskflow.emplace([&counter, i]() {
|
|
REQUIRE(counter == i); counter += 1;}
|
|
)
|
|
);
|
|
if(i>0) {
|
|
tasks[i-1].precede(tasks[i]);
|
|
}
|
|
}
|
|
executor.run(taskflow).get();
|
|
REQUIRE(counter == num_tasks);
|
|
REQUIRE(taskflow.num_tasks() == num_tasks);
|
|
}
|
|
|
|
SUBCASE("Broadcast"){
|
|
auto src = taskflow.emplace([&counter]() {counter -= 1;});
|
|
for(size_t i=1; i<num_tasks; i++){
|
|
silent_tasks.emplace_back(
|
|
taskflow.emplace([&counter]() {REQUIRE(counter == -1);})
|
|
);
|
|
src.precede(silent_tasks.back());
|
|
}
|
|
executor.run(taskflow).get();
|
|
REQUIRE(counter == - 1);
|
|
REQUIRE(taskflow.num_tasks() == num_tasks);
|
|
}
|
|
|
|
SUBCASE("Succeed"){
|
|
auto dst = taskflow.emplace([&]() { REQUIRE(counter == num_tasks - 1);});
|
|
for(size_t i=1;i<num_tasks;i++){
|
|
silent_tasks.emplace_back(
|
|
taskflow.emplace([&counter]() {counter += 1;})
|
|
);
|
|
dst.succeed(silent_tasks.back());
|
|
}
|
|
executor.run(taskflow).get();
|
|
REQUIRE(counter == num_tasks - 1);
|
|
REQUIRE(taskflow.num_tasks() == num_tasks);
|
|
}
|
|
|
|
SUBCASE("MapReduce"){
|
|
|
|
auto src = taskflow.emplace([&counter]() {counter = 0;});
|
|
|
|
auto dst = taskflow.emplace(
|
|
[&counter, num_tasks]() { REQUIRE(counter == num_tasks);}
|
|
);
|
|
|
|
for(size_t i=0;i<num_tasks;i++){
|
|
silent_tasks.emplace_back(
|
|
taskflow.emplace([&counter]() {counter += 1;})
|
|
);
|
|
src.precede(silent_tasks.back());
|
|
dst.succeed(silent_tasks.back());
|
|
}
|
|
executor.run(taskflow).get();
|
|
REQUIRE(taskflow.num_tasks() == num_tasks + 2);
|
|
}
|
|
|
|
SUBCASE("Linearize"){
|
|
for(size_t i=0;i<num_tasks;i++){
|
|
silent_tasks.emplace_back(
|
|
taskflow.emplace([&counter, i]() {
|
|
REQUIRE(counter == i); counter += 1;}
|
|
)
|
|
);
|
|
}
|
|
taskflow.linearize(silent_tasks);
|
|
executor.run(taskflow).get();
|
|
REQUIRE(counter == num_tasks);
|
|
REQUIRE(taskflow.num_tasks() == num_tasks);
|
|
}
|
|
|
|
SUBCASE("Kite"){
|
|
auto src = taskflow.emplace([&counter]() {counter = 0;});
|
|
for(size_t i=0;i<num_tasks;i++){
|
|
silent_tasks.emplace_back(
|
|
taskflow.emplace([&counter, i]() {
|
|
REQUIRE(counter == i); counter += 1; }
|
|
)
|
|
);
|
|
src.precede(silent_tasks.back());
|
|
}
|
|
taskflow.linearize(silent_tasks);
|
|
auto dst = taskflow.emplace(
|
|
[&counter, num_tasks]() { REQUIRE(counter == num_tasks);}
|
|
);
|
|
|
|
for(auto task : silent_tasks) dst.succeed(task);
|
|
executor.run(taskflow).get();
|
|
REQUIRE(taskflow.num_tasks() == num_tasks + 2);
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// Testcase: Creation
|
|
// --------------------------------------------------------
|
|
TEST_CASE("Creation" * doctest::timeout(300)) {
|
|
|
|
std::vector<int> dummy(1000, -1);
|
|
|
|
auto create_taskflow = [&] () {
|
|
for(int i=0; i<10; ++i) {
|
|
tf::Taskflow tf;
|
|
tf.for_each(dummy.begin(), dummy.end(), [] (int) {});
|
|
}
|
|
};
|
|
|
|
SUBCASE("One") {
|
|
create_taskflow();
|
|
REQUIRE(dummy.size() == 1000);
|
|
for(auto item : dummy) {
|
|
REQUIRE(item == -1);
|
|
}
|
|
}
|
|
|
|
SUBCASE("Two") {
|
|
std::thread t1(create_taskflow);
|
|
std::thread t2(create_taskflow);
|
|
t1.join();
|
|
t2.join();
|
|
REQUIRE(dummy.size() == 1000);
|
|
for(auto item : dummy) {
|
|
REQUIRE(item == -1);
|
|
}
|
|
}
|
|
|
|
SUBCASE("Four") {
|
|
std::thread t1(create_taskflow);
|
|
std::thread t2(create_taskflow);
|
|
std::thread t3(create_taskflow);
|
|
std::thread t4(create_taskflow);
|
|
t1.join();
|
|
t2.join();
|
|
t3.join();
|
|
t4.join();
|
|
REQUIRE(dummy.size() == 1000);
|
|
for(auto item : dummy) {
|
|
REQUIRE(item == -1);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// Testcase: Removal
|
|
// --------------------------------------------------------
|
|
TEST_CASE("Removal" * doctest::timeout(300)) {
|
|
|
|
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");
|
|
|
|
REQUIRE(a.num_successors() == 0);
|
|
REQUIRE(a.num_dependents() == 0);
|
|
REQUIRE(a.num_successors() == 0);
|
|
REQUIRE(a.num_dependents() == 0);
|
|
|
|
a.precede(b, c, d);
|
|
REQUIRE(a.num_successors() == 3);
|
|
REQUIRE(b.num_dependents() == 1);
|
|
REQUIRE(c.num_dependents() == 1);
|
|
REQUIRE(d.num_dependents() == 1);
|
|
|
|
taskflow.remove_dependency(a, b);
|
|
REQUIRE(a.num_successors() == 2);
|
|
REQUIRE(b.num_dependents() == 0);
|
|
|
|
taskflow.remove_dependency(a, c);
|
|
REQUIRE(a.num_successors() == 1);
|
|
REQUIRE(c.num_dependents() == 0);
|
|
|
|
taskflow.remove_dependency(a, d);
|
|
REQUIRE(a.num_successors() == 0);
|
|
REQUIRE(d.num_dependents() == 0);
|
|
|
|
a.precede(b, b, c, c, d, d);
|
|
REQUIRE(a.num_successors() == 6);
|
|
REQUIRE(b.num_dependents() == 2);
|
|
|
|
taskflow.remove_dependency(a, b);
|
|
REQUIRE(a.num_successors() == 4);
|
|
REQUIRE(b.num_dependents() == 0);
|
|
|
|
taskflow.remove_dependency(a, c);
|
|
REQUIRE(a.num_successors() == 2);
|
|
REQUIRE(b.num_dependents() == 0);
|
|
|
|
taskflow.remove_dependency(a, d);
|
|
REQUIRE(a.num_successors() == 0);
|
|
REQUIRE(d.num_dependents() == 0);
|
|
}
|
|
|
|
|
|
// --------------------------------------------------------
|
|
// Testcase: STDFunction
|
|
// --------------------------------------------------------
|
|
TEST_CASE("STDFunction" * doctest::timeout(300)) {
|
|
|
|
tf::Taskflow taskflow;
|
|
tf::Executor executor;
|
|
|
|
int counter = 0;
|
|
|
|
std::function<void()> func1 = [&] () { ++counter; };
|
|
std::function<int()> func2 = [&] () { ++counter; return 0; };
|
|
std::function<void()> func3 = [&] () { };
|
|
std::function<void()> func4 = [&] () { ++counter;};
|
|
|
|
// scenario 1
|
|
auto A = taskflow.emplace(func1);
|
|
auto B = taskflow.emplace(func2);
|
|
auto C = taskflow.emplace(func3);
|
|
auto D = taskflow.emplace(func4);
|
|
A.precede(B);
|
|
B.precede(C, D);
|
|
executor.run(taskflow).wait();
|
|
REQUIRE(counter == 2);
|
|
|
|
// scenario 2
|
|
counter = 0;
|
|
A.work(func1);
|
|
B.work(func2);
|
|
C.work(func4);
|
|
D.work(func3);
|
|
executor.run(taskflow).wait();
|
|
REQUIRE(counter == 3);
|
|
|
|
// scenario 3
|
|
taskflow.clear();
|
|
std::tie(A, B, C, D) = taskflow.emplace(
|
|
std::move(func1), std::move(func2), std::move(func3), std::move(func4)
|
|
);
|
|
A.precede(B);
|
|
B.precede(C, D);
|
|
counter = 0;
|
|
executor.run(taskflow).wait();
|
|
REQUIRE(counter == 2);
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// Testcase: Iterators
|
|
// --------------------------------------------------------
|
|
TEST_CASE("Iterators" * doctest::timeout(300)) {
|
|
|
|
SUBCASE("Order") {
|
|
tf::Taskflow taskflow;
|
|
|
|
auto A = taskflow.emplace([](){}).name("A");
|
|
auto B = taskflow.emplace([](){}).name("B");
|
|
auto C = taskflow.emplace([](){}).name("C");
|
|
auto D = taskflow.emplace([](){}).name("D");
|
|
auto E = taskflow.emplace([](){}).name("E");
|
|
|
|
A.precede(B, C, D, E);
|
|
E.succeed(B, C, D);
|
|
|
|
A.for_each_successor([&, i=0] (tf::Task s) mutable {
|
|
switch(i++) {
|
|
case 0:
|
|
REQUIRE(s == B);
|
|
break;
|
|
case 1:
|
|
REQUIRE(s == C);
|
|
break;
|
|
case 2:
|
|
REQUIRE(s == D);
|
|
break;
|
|
case 3:
|
|
REQUIRE(s == E);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
});
|
|
|
|
E.for_each_dependent([&, i=0](tf::Task s) mutable {
|
|
switch(i++) {
|
|
case 0:
|
|
REQUIRE(s == A);
|
|
break;
|
|
case 1:
|
|
REQUIRE(s == B);
|
|
break;
|
|
case 2:
|
|
REQUIRE(s == C);
|
|
break;
|
|
case 3:
|
|
REQUIRE(s == D);
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
|
|
SUBCASE("Generic") {
|
|
tf::Taskflow taskflow;
|
|
|
|
auto A = taskflow.emplace([](){}).name("A");
|
|
auto B = taskflow.emplace([](){}).name("B");
|
|
auto C = taskflow.emplace([](){}).name("C");
|
|
auto D = taskflow.emplace([](){}).name("D");
|
|
auto E = taskflow.emplace([](){}).name("E");
|
|
|
|
std::vector<tf::Task> tasks;
|
|
|
|
taskflow.for_each_task([&tasks](tf::Task s){
|
|
tasks.push_back(s);
|
|
});
|
|
|
|
REQUIRE(std::find(tasks.begin(), tasks.end(), A) != tasks.end());
|
|
|
|
A.precede(B);
|
|
|
|
A.for_each_successor([B](tf::Task s){ REQUIRE(s==B); });
|
|
B.for_each_dependent([A](tf::Task s){ REQUIRE(s==A); });
|
|
|
|
A.precede(C);
|
|
A.precede(D);
|
|
A.precede(E);
|
|
C.precede(B);
|
|
D.precede(B);
|
|
E.precede(B);
|
|
|
|
int counter{0}, a{0}, b{0}, c{0}, d{0}, e{0};
|
|
A.for_each_successor([&](tf::Task s) {
|
|
counter++;
|
|
if(s == A) ++a;
|
|
if(s == B) ++b;
|
|
if(s == C) ++c;
|
|
if(s == D) ++d;
|
|
if(s == E) ++e;
|
|
});
|
|
REQUIRE(counter == A.num_successors());
|
|
REQUIRE(a==0);
|
|
REQUIRE(b==1);
|
|
REQUIRE(c==1);
|
|
REQUIRE(d==1);
|
|
REQUIRE(e==1);
|
|
|
|
counter = a = b = c = d = e = 0;
|
|
B.for_each_dependent([&](tf::Task s) {
|
|
counter++;
|
|
if(s == A) ++a;
|
|
if(s == B) ++b;
|
|
if(s == C) ++c;
|
|
if(s == D) ++d;
|
|
if(s == E) ++e;
|
|
});
|
|
|
|
REQUIRE(counter == B.num_dependents());
|
|
REQUIRE(a == 1);
|
|
REQUIRE(b == 0);
|
|
REQUIRE(c == 1);
|
|
REQUIRE(d == 1);
|
|
REQUIRE(e == 1);
|
|
|
|
A.for_each_successor([](tf::Task s){
|
|
s.name("A");
|
|
});
|
|
|
|
REQUIRE(A.name() == "A");
|
|
REQUIRE(B.name() == "A");
|
|
REQUIRE(C.name() == "A");
|
|
REQUIRE(D.name() == "A");
|
|
REQUIRE(E.name() == "A");
|
|
|
|
B.for_each_dependent([](tf::Task s){
|
|
s.name("B");
|
|
});
|
|
|
|
REQUIRE(A.name() == "B");
|
|
REQUIRE(B.name() == "A");
|
|
REQUIRE(C.name() == "B");
|
|
REQUIRE(D.name() == "B");
|
|
REQUIRE(E.name() == "B");
|
|
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// Testcase: Hash
|
|
// --------------------------------------------------------
|
|
TEST_CASE("Hash" * doctest::timeout(300)) {
|
|
|
|
std::hash<tf::Task> hash;
|
|
|
|
// empty hash
|
|
tf::Task t1, t2;
|
|
|
|
REQUIRE(hash(t1) == hash(t2));
|
|
|
|
tf::Taskflow taskflow;
|
|
|
|
t1 = taskflow.emplace([](){});
|
|
|
|
REQUIRE(((hash(t1) != hash(t2)) || (hash(t1) == hash(t2) && t1 != t2)));
|
|
|
|
t2 = taskflow.emplace([](){});
|
|
|
|
REQUIRE(((hash(t1) != hash(t2)) || (hash(t1) == hash(t2) && t1 != t2)));
|
|
|
|
t2 = t1;
|
|
|
|
REQUIRE(hash(t1) == hash(t2));
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// Testcase: SequentialRuns
|
|
// --------------------------------------------------------
|
|
void sequential_runs(unsigned W) {
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
size_t num_tasks = 100;
|
|
|
|
tf::Executor executor(W);
|
|
tf::Taskflow taskflow;
|
|
|
|
std::atomic<int> counter {0};
|
|
std::vector<tf::Task> silent_tasks;
|
|
|
|
for(size_t i=0;i<num_tasks;i++){
|
|
silent_tasks.emplace_back(
|
|
taskflow.emplace([&counter]() {counter += 1;})
|
|
);
|
|
}
|
|
|
|
SUBCASE("RunOnce"){
|
|
auto fu = executor.run(taskflow);
|
|
REQUIRE(taskflow.num_tasks() == num_tasks);
|
|
fu.get();
|
|
REQUIRE(counter == num_tasks);
|
|
}
|
|
|
|
SUBCASE("WaitForAll") {
|
|
executor.run(taskflow);
|
|
executor.wait_for_all();
|
|
REQUIRE(counter == num_tasks);
|
|
}
|
|
|
|
SUBCASE("RunWithFuture") {
|
|
|
|
std::atomic<size_t> count {0};
|
|
tf::Taskflow f;
|
|
auto A = f.emplace([&](){ count ++; });
|
|
auto B = f.emplace([&](tf::Subflow& subflow){
|
|
count ++;
|
|
auto B1 = subflow.emplace([&](){ count++; });
|
|
auto B2 = subflow.emplace([&](){ count++; });
|
|
auto B3 = subflow.emplace([&](){ count++; });
|
|
B1.precede(B3); B2.precede(B3);
|
|
});
|
|
auto C = f.emplace([&](){ count ++; });
|
|
auto D = f.emplace([&](){ count ++; });
|
|
|
|
A.precede(B, C);
|
|
B.precede(D);
|
|
C.precede(D);
|
|
|
|
std::list<tf::Future<void>> fu_list;
|
|
for(size_t i=0; i<500; i++) {
|
|
if(i == 499) {
|
|
executor.run(f).get(); // Synchronize the first 500 runs
|
|
executor.run_n(f, 500); // Run 500 times more
|
|
}
|
|
else if(i % 2) {
|
|
fu_list.push_back(executor.run(f));
|
|
}
|
|
else {
|
|
fu_list.push_back(executor.run(f, [&, i=i](){
|
|
REQUIRE(count == (i+1)*7); })
|
|
);
|
|
}
|
|
}
|
|
|
|
executor.wait_for_all();
|
|
|
|
for(auto& fu: fu_list) {
|
|
REQUIRE(fu.valid());
|
|
REQUIRE(fu.wait_for(std::chrono::seconds(1)) == std::future_status::ready);
|
|
}
|
|
|
|
REQUIRE(count == 7000);
|
|
|
|
}
|
|
|
|
SUBCASE("RunWithChange") {
|
|
std::atomic<size_t> count {0};
|
|
tf::Taskflow f;
|
|
auto A = f.emplace([&](){ count ++; });
|
|
auto B = f.emplace([&](tf::Subflow& subflow){
|
|
count ++;
|
|
auto B1 = subflow.emplace([&](){ count++; });
|
|
auto B2 = subflow.emplace([&](){ count++; });
|
|
auto B3 = subflow.emplace([&](){ count++; });
|
|
B1.precede(B3); B2.precede(B3);
|
|
});
|
|
auto C = f.emplace([&](){ count ++; });
|
|
auto D = f.emplace([&](){ count ++; });
|
|
|
|
A.precede(B, C);
|
|
B.precede(D);
|
|
C.precede(D);
|
|
|
|
executor.run_n(f, 10).get();
|
|
REQUIRE(count == 70);
|
|
|
|
auto E = f.emplace([](){});
|
|
D.precede(E);
|
|
executor.run_n(f, 10).get();
|
|
REQUIRE(count == 140);
|
|
|
|
auto F = f.emplace([](){});
|
|
E.precede(F);
|
|
executor.run_n(f, 10);
|
|
executor.wait_for_all();
|
|
REQUIRE(count == 210);
|
|
|
|
}
|
|
|
|
SUBCASE("RunWithPred") {
|
|
std::atomic<size_t> count {0};
|
|
tf::Taskflow f;
|
|
auto A = f.emplace([&](){ count ++; });
|
|
auto B = f.emplace([&](tf::Subflow& subflow){
|
|
count ++;
|
|
auto B1 = subflow.emplace([&](){ count++; });
|
|
auto B2 = subflow.emplace([&](){ count++; });
|
|
auto B3 = subflow.emplace([&](){ count++; });
|
|
B1.precede(B3); B2.precede(B3);
|
|
});
|
|
auto C = f.emplace([&](){ count ++; });
|
|
auto D = f.emplace([&](){ count ++; });
|
|
|
|
A.precede(B, C);
|
|
B.precede(D);
|
|
C.precede(D);
|
|
|
|
executor.run_until(f, [run=10]() mutable { return run-- == 0; },
|
|
[&](){
|
|
REQUIRE(count == 70);
|
|
count = 0;
|
|
}
|
|
).get();
|
|
|
|
|
|
executor.run_until(f, [run=10]() mutable { return run-- == 0; },
|
|
[&](){
|
|
REQUIRE(count == 70);
|
|
count = 0;
|
|
});
|
|
|
|
executor.run_until(f, [run=10]() mutable { return run-- == 0; },
|
|
[&](){
|
|
REQUIRE(count == 70);
|
|
count = 0;
|
|
}
|
|
).get();
|
|
|
|
}
|
|
|
|
SUBCASE("MultipleRuns") {
|
|
std::atomic<size_t> count(0);
|
|
|
|
tf::Taskflow tf1, tf2, tf3, tf4;
|
|
|
|
for(size_t n=0; n<16; ++n) {
|
|
tf1.emplace([&](){count.fetch_add(1, std::memory_order_relaxed);});
|
|
}
|
|
|
|
for(size_t n=0; n<1024; ++n) {
|
|
tf2.emplace([&](){count.fetch_add(1, std::memory_order_relaxed);});
|
|
}
|
|
|
|
for(size_t n=0; n<32; ++n) {
|
|
tf3.emplace([&](){count.fetch_add(1, std::memory_order_relaxed);});
|
|
}
|
|
|
|
for(size_t n=0; n<128; ++n) {
|
|
tf4.emplace([&](){count.fetch_add(1, std::memory_order_relaxed);});
|
|
}
|
|
|
|
for(int i=0; i<200; ++i) {
|
|
executor.run(tf1);
|
|
executor.run(tf2);
|
|
executor.run(tf3);
|
|
executor.run(tf4);
|
|
}
|
|
executor.wait_for_all();
|
|
REQUIRE(count == 240000);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
TEST_CASE("SerialRuns.1thread" * doctest::timeout(300)) {
|
|
sequential_runs(1);
|
|
}
|
|
|
|
TEST_CASE("SerialRuns.2threads" * doctest::timeout(300)) {
|
|
sequential_runs(2);
|
|
}
|
|
|
|
TEST_CASE("SerialRuns.3threads" * doctest::timeout(300)) {
|
|
sequential_runs(3);
|
|
}
|
|
|
|
TEST_CASE("SerialRuns.4threads" * doctest::timeout(300)) {
|
|
sequential_runs(4);
|
|
}
|
|
|
|
TEST_CASE("SerialRuns.5threads" * doctest::timeout(300)) {
|
|
sequential_runs(5);
|
|
}
|
|
|
|
TEST_CASE("SerialRuns.6threads" * doctest::timeout(300)) {
|
|
sequential_runs(6);
|
|
}
|
|
|
|
TEST_CASE("SerialRuns.7threads" * doctest::timeout(300)) {
|
|
sequential_runs(7);
|
|
}
|
|
|
|
TEST_CASE("SerialRuns.8threads" * doctest::timeout(300)) {
|
|
sequential_runs(8);
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// Testcase:: RunAndWait
|
|
// --------------------------------------------------------
|
|
|
|
TEST_CASE("RunAndWait.Simple") {
|
|
|
|
// create an executor and a taskflow
|
|
tf::Executor executor(2);
|
|
tf::Taskflow taskflow("Demo");
|
|
|
|
REQUIRE_THROWS(executor.corun(taskflow));
|
|
|
|
int counter{0};
|
|
|
|
// taskflow to run by the main taskflow
|
|
tf::Taskflow others;
|
|
tf::Task A = others.emplace([&](){ counter++; });
|
|
tf::Task B = others.emplace([&](){ counter++; });
|
|
A.precede(B);
|
|
|
|
// main taskflow
|
|
tf::Task C = taskflow.emplace([&](){
|
|
executor.corun(others);
|
|
REQUIRE(counter == 2);
|
|
});
|
|
tf::Task D = taskflow.emplace([&](){
|
|
executor.corun(others);
|
|
REQUIRE(counter == 4);
|
|
});
|
|
C.precede(D);
|
|
|
|
executor.run(taskflow).wait();
|
|
|
|
// run others again
|
|
executor.run(others).wait();
|
|
|
|
REQUIRE(counter == 6);
|
|
}
|
|
|
|
TEST_CASE("RunAndWait.Complex") {
|
|
|
|
const size_t N = 100;
|
|
const size_t T = 1000;
|
|
|
|
// create an executor and a taskflow
|
|
tf::Executor executor(2);
|
|
tf::Taskflow taskflow;
|
|
|
|
std::array<tf::Taskflow, N> taskflows;
|
|
|
|
std::atomic<size_t> counter{0};
|
|
|
|
for(size_t n=0; n<N; n++) {
|
|
for(size_t i=0; i<T; i++) {
|
|
taskflows[n].emplace([&](){ counter++; });
|
|
}
|
|
taskflow.emplace([&executor, &tf=taskflows[n]](){
|
|
executor.corun(tf);
|
|
//executor.run(tf).wait();
|
|
});
|
|
}
|
|
|
|
executor.run(taskflow).wait();
|
|
|
|
REQUIRE(counter == T*N);
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// Testcase: WorkerID
|
|
// --------------------------------------------------------
|
|
void worker_id(unsigned w) {
|
|
|
|
tf::Taskflow taskflow;
|
|
tf::Executor executor(w);
|
|
|
|
for(int i=0; i<1000; i++) {
|
|
auto A = taskflow.emplace([&](){
|
|
auto id = executor.this_worker_id();
|
|
REQUIRE(id>=0);
|
|
REQUIRE(id< w);
|
|
});
|
|
|
|
auto B = taskflow.emplace([&](tf::Subflow& sf){
|
|
auto id = executor.this_worker_id();
|
|
REQUIRE(id>=0);
|
|
REQUIRE(id< w);
|
|
sf.emplace([&](){
|
|
auto sfid = executor.this_worker_id();
|
|
REQUIRE(sfid>=0);
|
|
REQUIRE(sfid< w);
|
|
});
|
|
sf.emplace([&](tf::Subflow&){
|
|
auto sfid = executor.this_worker_id();
|
|
REQUIRE(sfid>=0);
|
|
REQUIRE(sfid< w);
|
|
});
|
|
});
|
|
|
|
A.precede(B);
|
|
}
|
|
|
|
executor.run_n(taskflow, 100).wait();
|
|
}
|
|
|
|
TEST_CASE("WorkerID.1thread") {
|
|
worker_id(1);
|
|
}
|
|
|
|
TEST_CASE("WorkerID.2threads") {
|
|
worker_id(2);
|
|
}
|
|
|
|
TEST_CASE("WorkerID.3threads") {
|
|
worker_id(3);
|
|
}
|
|
|
|
TEST_CASE("WorkerID.4threads") {
|
|
worker_id(4);
|
|
}
|
|
|
|
TEST_CASE("WorkerID.5threads") {
|
|
worker_id(5);
|
|
}
|
|
|
|
TEST_CASE("WorkerID.6threads") {
|
|
worker_id(6);
|
|
}
|
|
|
|
TEST_CASE("WorkerID.7threads") {
|
|
worker_id(7);
|
|
}
|
|
|
|
TEST_CASE("WorkerID.8threads") {
|
|
worker_id(8);
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// Testcase: ParallelRuns
|
|
// --------------------------------------------------------
|
|
void parallel_runs(unsigned w) {
|
|
|
|
std::atomic<int> counter;
|
|
std::vector<std::thread> threads;
|
|
|
|
auto make_taskflow = [&] (tf::Taskflow& tf) {
|
|
for(int i=0; i<1024; i++) {
|
|
auto A = tf.emplace([&] () {
|
|
counter.fetch_add(1, std::memory_order_relaxed);
|
|
});
|
|
auto B = tf.emplace([&] () {
|
|
counter.fetch_add(1, std::memory_order_relaxed);
|
|
});
|
|
A.precede(B);
|
|
}
|
|
};
|
|
|
|
SUBCASE("RunAndWait") {
|
|
tf::Executor executor(w);
|
|
counter = 0;
|
|
for(int t=0; t<32; t++) {
|
|
threads.emplace_back([&] () {
|
|
tf::Taskflow taskflow;
|
|
make_taskflow(taskflow);
|
|
executor.run(taskflow).wait();
|
|
});
|
|
}
|
|
|
|
for(auto& t : threads) {
|
|
t.join();
|
|
}
|
|
threads.clear();
|
|
|
|
REQUIRE(counter.load() == 32*1024*2);
|
|
|
|
}
|
|
|
|
SUBCASE("RunAndWaitForAll") {
|
|
tf::Executor executor(w);
|
|
counter = 0;
|
|
std::vector<std::unique_ptr<tf::Taskflow>> taskflows(32);
|
|
std::atomic<size_t> barrier(0);
|
|
for(int t=0; t<32; t++) {
|
|
threads.emplace_back([&, t=t] () {
|
|
taskflows[t] = std::make_unique<tf::Taskflow>();
|
|
make_taskflow(*taskflows[t]);
|
|
executor.run(*taskflows[t]);
|
|
++barrier; // make sure all runs are issued
|
|
});
|
|
}
|
|
|
|
while(barrier != 32);
|
|
executor.wait_for_all();
|
|
REQUIRE(counter.load() == 32*1024*2);
|
|
|
|
for(auto& t : threads) {
|
|
t.join();
|
|
}
|
|
threads.clear();
|
|
}
|
|
}
|
|
|
|
|
|
TEST_CASE("ParallelRuns.1thread" * doctest::timeout(300)) {
|
|
parallel_runs(1);
|
|
}
|
|
|
|
TEST_CASE("ParallelRuns.2threads" * doctest::timeout(300)) {
|
|
parallel_runs(2);
|
|
}
|
|
|
|
TEST_CASE("ParallelRuns.3threads" * doctest::timeout(300)) {
|
|
parallel_runs(3);
|
|
}
|
|
|
|
TEST_CASE("ParallelRuns.4threads" * doctest::timeout(300)) {
|
|
parallel_runs(4);
|
|
}
|
|
|
|
TEST_CASE("ParallelRuns.5threads" * doctest::timeout(300)) {
|
|
parallel_runs(5);
|
|
}
|
|
|
|
TEST_CASE("ParallelRuns.6threads" * doctest::timeout(300)) {
|
|
parallel_runs(6);
|
|
}
|
|
|
|
TEST_CASE("ParallelRuns.7threads" * doctest::timeout(300)) {
|
|
parallel_runs(7);
|
|
}
|
|
|
|
TEST_CASE("ParallelRuns.8threads" * doctest::timeout(300)) {
|
|
parallel_runs(8);
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// Testcase: NestedRuns
|
|
// --------------------------------------------------------
|
|
void nested_runs(unsigned w) {
|
|
|
|
int counter {0};
|
|
|
|
struct A {
|
|
|
|
tf::Executor executor;
|
|
tf::Taskflow taskflow;
|
|
|
|
int& counter;
|
|
|
|
A(unsigned w, int& c) : executor{w}, counter{c} { }
|
|
|
|
void run()
|
|
{
|
|
taskflow.clear();
|
|
auto A1 = taskflow.emplace([&]() { counter++; });
|
|
auto A2 = taskflow.emplace([&]() { counter++; });
|
|
A1.precede(A2);
|
|
executor.run_n(taskflow, 10).wait();
|
|
}
|
|
|
|
};
|
|
|
|
struct B {
|
|
|
|
tf::Taskflow taskflow;
|
|
tf::Executor executor;
|
|
|
|
int& counter;
|
|
|
|
A a_sim;
|
|
|
|
B(unsigned w, int& c) : executor{w}, counter{c}, a_sim{w, c} { }
|
|
|
|
void run()
|
|
{
|
|
taskflow.clear();
|
|
auto B1 = taskflow.emplace([&] () { ++counter; });
|
|
auto B2 = taskflow.emplace([&] () { ++counter; a_sim.run(); });
|
|
B1.precede(B2);
|
|
executor.run_n(taskflow, 100).wait();
|
|
}
|
|
};
|
|
|
|
struct C {
|
|
|
|
tf::Taskflow taskflow;
|
|
tf::Executor executor;
|
|
|
|
int& counter;
|
|
|
|
B b_sim;
|
|
|
|
C(unsigned w, int& c) : executor{w}, counter{c}, b_sim{w, c} { }
|
|
|
|
void run()
|
|
{
|
|
taskflow.clear();
|
|
auto C1 = taskflow.emplace([&] () { ++counter; });
|
|
auto C2 = taskflow.emplace([&] () { ++counter; b_sim.run(); });
|
|
C1.precede(C2);
|
|
executor.run_n(taskflow, 100).wait();
|
|
}
|
|
};
|
|
|
|
C c(w, counter);
|
|
c.run();
|
|
|
|
REQUIRE(counter == 220200);
|
|
}
|
|
|
|
TEST_CASE("NestedRuns.1thread") {
|
|
nested_runs(1);
|
|
}
|
|
|
|
TEST_CASE("NestedRuns.2threads") {
|
|
nested_runs(2);
|
|
}
|
|
|
|
TEST_CASE("NestedRuns.3threads") {
|
|
nested_runs(3);
|
|
}
|
|
|
|
TEST_CASE("NestedRuns.4threads") {
|
|
nested_runs(4);
|
|
}
|
|
|
|
TEST_CASE("NestedRuns.8threads") {
|
|
nested_runs(8);
|
|
}
|
|
|
|
TEST_CASE("NestedRuns.16threads") {
|
|
nested_runs(16);
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// Testcase: Observer
|
|
// --------------------------------------------------------
|
|
|
|
void observer(unsigned w) {
|
|
|
|
tf::Executor executor(w);
|
|
|
|
auto observer = executor.make_observer<tf::ChromeObserver>();
|
|
|
|
tf::Taskflow taskflowA;
|
|
std::vector<tf::Task> tasks;
|
|
// Static tasking
|
|
for(auto i=0; i < 64; i ++) {
|
|
tasks.emplace_back(taskflowA.emplace([](){}));
|
|
}
|
|
|
|
// Randomly specify dependency
|
|
for(auto i=0; i < 64; i ++) {
|
|
for(auto j=i+1; j < 64; j++) {
|
|
if(rand()%2 == 0) {
|
|
tasks[i].precede(tasks[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
executor.run_n(taskflowA, 16).get();
|
|
|
|
REQUIRE(observer->num_tasks() == 64*16);
|
|
|
|
observer->clear();
|
|
REQUIRE(observer->num_tasks() == 0);
|
|
tasks.clear();
|
|
|
|
}
|
|
|
|
TEST_CASE("Observer.1thread" * doctest::timeout(300)) {
|
|
observer(1);
|
|
}
|
|
|
|
TEST_CASE("Observer.2threads" * doctest::timeout(300)) {
|
|
observer(2);
|
|
}
|
|
|
|
TEST_CASE("Observer.3threads" * doctest::timeout(300)) {
|
|
observer(3);
|
|
}
|
|
|
|
TEST_CASE("Observer.4threads" * doctest::timeout(300)) {
|
|
observer(4);
|
|
}
|
|
|
|
|