40 template <
typename... C, std::enable_if_t<(
sizeof...(C)>1),
void>* =
nullptr>
64 template <
typename... C, std::enable_if_t<(
sizeof...(C)>1),
void>* =
nullptr>
84 template <
typename I,
typename C>
104 template <
typename I,
typename C, std::enable_if_t<std::is_arithmetic_v<I>,
void>* =
nullptr >
124 template <
typename I,
typename T,
typename B>
142 template <
typename I,
typename T>
160 template <
typename I,
typename T>
184 template <
typename I,
typename T,
typename B,
typename U>
211 template <
typename I,
typename T,
typename B,
typename P,
typename U>
279 template <
typename L>
282 template <
typename I>
283 size_t _estimate_chunk_size(I, I, I);
287 inline FlowBuilder::FlowBuilder(Graph& graph) :
293 from._node->precede(*(to._node));
318 auto& node = _graph.emplace_back();
323 template <
typename... C, std::enable_if_t<(
sizeof...(C)>1),
void>*>
329 template <
typename I,
typename C>
335 auto d = std::distance(beg, end);
348 if constexpr(std::is_same_v<category, std::random_access_iterator_tag>) {
349 size_t r = std::distance(beg, end);
350 std::advance(e, std::min(r, g));
354 for(
size_t i=0; i<g && e != end; ++e, ++i);
359 std::for_each(beg, e, c);
361 source.precede(task);
362 task.precede(target);
368 return std::make_pair(source, target);
375 std::enable_if_t<std::is_arithmetic_v<I>,
void>*
379 using T = std::decay_t<I>;
381 if((s == 0) || (beg < end && s <= 0) || (beg > end && s >=0) ) {
382 TF_THROW(Error::FLOW_BUILDER,
383 "invalid range [", beg,
", ", end,
") with step size ", s
391 g = _estimate_chunk_size(beg, end, s);
395 if constexpr(std::is_integral_v<T>) {
397 auto offset =
static_cast<T
>(g) * s;
402 auto e = std::min(beg + offset, end);
404 for(
auto i=beg; i<e; i+=s) {
408 source.precede(task);
409 task.precede(target);
416 auto e = std::max(beg + offset, end);
418 for(
auto i=beg; i>e; i+=s) {
422 source.precede(task);
423 task.precede(target);
429 else if constexpr(std::is_floating_point_v<T>) {
432 for(
auto i=beg; (beg<end ? i<end : i>end); i+=s, ++N) {
436 for(
size_t n=0; n<N; ++n) {
443 source.precede(task);
444 task.precede(target);
452 for(
size_t n=0; n<N; ++n) {
457 source.precede(task);
458 task.precede(target);
462 return std::make_pair(source, target);
467 template <
typename I,
typename T>
469 return reduce(beg, end, result, [] (
const auto& l,
const auto& r) {
470 return std::min(l, r);
476 template <
typename I,
typename T>
478 return reduce(beg, end, result, [] (
const auto& l,
const auto& r) {
479 return std::max(l, r);
484 template <
typename I,
typename T,
typename B,
typename U>
490 size_t d = std::distance(beg, end);
492 size_t g = std::max((d + w - 1) / w,
size_t{2});
504 if constexpr(std::is_same_v<category, std::random_access_iterator_tag>) {
505 size_t r = std::distance(beg, end);
506 std::advance(e, std::min(r, g));
510 for(
size_t i=0; i<g && e != end; ++e, ++i);
514 auto [task, future] =
emplace([beg, e, bop, uop] ()
mutable {
515 auto init = uop(*beg);
516 for(++beg; beg != e; ++beg) {
517 init = bop(std::move(init), uop(*beg));
521 source.precede(task);
522 task.precede(target);
523 futures.push_back(std::move(future));
530 target.work([&result, futures=MoC{std::move(futures)}, bop] () {
531 for(
auto& fu : futures.object) {
532 result = bop(std::move(result), fu.get());
536 return std::make_pair(source, target);
540 template <
typename I,
typename T,
typename B,
typename P,
typename U>
546 size_t d = std::distance(beg, end);
548 size_t g = std::max((d + w - 1) / w,
size_t{2});
560 if constexpr(std::is_same_v<category, std::random_access_iterator_tag>) {
561 size_t r = std::distance(beg, end);
562 std::advance(e, std::min(r, g));
566 for(
size_t i=0; i<g && e != end; ++e, ++i);
570 auto [task, future] =
emplace([beg, e, uop, pop] ()
mutable {
571 auto init = uop(*beg);
572 for(++beg; beg != e; ++beg) {
573 init = pop(std::move(init), *beg);
577 source.precede(task);
578 task.precede(target);
579 futures.push_back(std::move(future));
586 target.work([&result, futures=MoC{std::move(futures)}, bop] () {
587 for(
auto& fu : futures.object) {
588 result = bop(std::move(result), fu.get());
592 return std::make_pair(source, target);
596 template <
typename I>
597 size_t FlowBuilder::_estimate_chunk_size(I beg, I end, I step) {
599 using T = std::decay_t<I>;
604 if constexpr(std::is_integral_v<T>) {
606 N = (end - beg + step - 1) / step;
609 N = (end - beg + step + 1) / step;
612 else if constexpr(std::is_floating_point_v<T>) {
613 N =
static_cast<size_t>(std::ceil((end - beg) / step));
616 static_assert(dependent_false_v<T>,
"can't deduce chunk size");
619 return (N + w - 1) / w;
624 template <
typename L>
625 void FlowBuilder::_linearize(L& keys) {
627 auto itr = keys.begin();
628 auto end = keys.end();
636 for(++nxt; nxt != end; ++nxt, ++itr) {
637 itr->_node->precede(*(nxt->_node));
652 template <
typename I,
typename T,
typename B>
657 size_t d = std::distance(beg, end);
659 size_t g = std::max((d + w - 1) / w,
size_t{2});
671 if constexpr(std::is_same_v<category, std::random_access_iterator_tag>) {
672 size_t r = std::distance(beg, end);
673 std::advance(e, std::min(r, g));
677 for(
size_t i=0; i<g && e != end; ++e, ++i);
681 auto [task, future] =
emplace([beg, e, op] ()
mutable {
683 for(++beg; beg != e; ++beg) {
684 init = op(std::move(init), *beg);
688 source.precede(task);
689 task.precede(target);
690 futures.push_back(std::move(future));
697 target.work([&result, futures=MoC{std::move(futures)}, op] () {
698 for(
auto& fu : futures.object) {
699 result = op(std::move(result), fu.get());
703 return std::make_pair(source, target);
717 template <
typename... Args>
733 bool detached()
const;
742 bool _detached {
false};
746 template <
typename... Args>
747 SubflowBuilder::SubflowBuilder(Args&&... args) :
772 template <
typename C>
776 if constexpr(std::is_invocable_v<C, SubflowBuilder&>) {
778 using R = std::invoke_result_t<C, SubflowBuilder&>;
780 auto fu = p.get_future();
782 if constexpr(std::is_same_v<void, R>) {
783 auto& node = _graph.emplace_back([p=MoC(std::move(p)), c=std::forward<C>(c)]
785 if(fb._graph.empty()) {
788 if(fb.
detached() || fb._graph.empty()) {
796 return std::make_pair(
Task(node), std::move(fu));
799 auto& node = _graph.emplace_back(
800 [p=MoC(std::move(p)), c=std::forward<C>(c), r=std::optional<R>()]
802 if(fb._graph.empty()) {
804 if(fb.
detached() || fb._graph.empty()) {
805 p.get().set_value(std::move(*r));
810 p.get().set_value(std::move(*r));
813 return std::make_pair(
Task(node), std::move(fu));
817 else if constexpr(std::is_invocable_v<C>) {
819 using R = std::invoke_result_t<C>;
821 auto fu = p.get_future();
823 if constexpr(std::is_same_v<void, R>) {
824 auto& node = _graph.emplace_back(
825 [p=MoC(std::move(p)), c=std::forward<C>(c)]()
mutable {
830 return std::make_pair(
Task(node), std::move(fu));
833 auto& node = _graph.emplace_back(
834 [p=MoC(std::move(p)), c=std::forward<C>(c)]()
mutable {
835 p.get().set_value(c());
838 return std::make_pair(
Task(node), std::move(fu));
842 static_assert(dependent_false_v<C>,
"invalid task work type");
847 template <
typename... C, std::enable_if_t<(
sizeof...(C)>1),
void>*>
849 return std::make_tuple(
emplace(std::forward<C>(cs))...);
853 template <
typename C>
856 if constexpr(std::is_invocable_v<C, SubflowBuilder&>) {
857 auto& n = _graph.emplace_back(
860 if(fb._graph.empty()) {
867 else if constexpr(std::is_invocable_v<C>) {
868 auto& n = _graph.emplace_back(std::forward<C>(c));
872 static_assert(dependent_false_v<C>,
"invalid task work type");
bool joined() const
queries if the subflow will join its parent task
Definition: flow_builder.hpp:767
void linearize(std::vector< Task > &tasks)
adds adjacent dependency links to a linear list of tasks
Definition: flow_builder.hpp:642
bool detached() const
queries if the subflow will be detached from its parent task
Definition: flow_builder.hpp:762
void detach()
enables the subflow to detach from its parent task
Definition: flow_builder.hpp:757
void broadcast(Task A, std::vector< Task > &others)
adds dependency links from one task A to many tasks
Definition: flow_builder.hpp:297
std::pair< Task, Task > transform_reduce(I beg, I end, T &result, B &&bop, U &&uop)
constructs a task dependency graph of parallel transformation and reduction
Definition: flow_builder.hpp:485
Task & gather(Ts &&... tasks)
adds precedence links from other tasks to this
Definition: task.hpp:224
Definition: taskflow.hpp:6
auto silent_emplace(C &&callable)
creates a task from a given callable object without access to the result
Definition: flow_builder.hpp:854
T hardware_concurrency(T... args)
Task placeholder()
creates an empty task
Definition: flow_builder.hpp:317
std::pair< Task, Task > reduce_max(I beg, I end, T &result)
constructs a task dependency graph of parallel reduction through std::max
Definition: flow_builder.hpp:477
The building blocks of dynamic tasking.
Definition: flow_builder.hpp:713
std::pair< Task, Task > parallel_for(I beg, I end, C &&callable, size_t chunk=0)
constructs a task dependency graph of range-based parallel_for
Definition: flow_builder.hpp:330
auto emplace(C &&callable)
creates a task from a given callable object
Definition: flow_builder.hpp:773
void precede(Task A, Task B)
adds a dependency link from task A to task B
Definition: flow_builder.hpp:292
void gather(std::vector< Task > &others, Task A)
adds dependency links from many tasks to one task A
Definition: flow_builder.hpp:307
The building blocks of task dependency graphs.
Definition: flow_builder.hpp:13
void join()
enables the subflow to join its parent task
Definition: flow_builder.hpp:752
Handle to modify and access a task.
Definition: task.hpp:18
Task & precede(Ts &&... tasks)
adds precedence links from this to other tasks
Definition: task.hpp:205
std::pair< Task, Task > reduce(I beg, I end, T &result, B &&bop)
construct a task dependency graph of parallel reduction
Definition: flow_builder.hpp:653
std::pair< Task, Task > reduce_min(I beg, I end, T &result)
constructs a task dependency graph of parallel reduction through std::min
Definition: flow_builder.hpp:468