40template <
typename T,
size_t N>
41class IndexRangesPartitioner {
47 explicit IndexRangesPartitioner(
const R& ranges);
51 size_t active_rank()
const;
58 bool for_each_box(
size_t flat_beg,
size_t flat_end, F&& visit)
requires (N == 1);
68 bool for_each_box(
size_t flat_beg,
size_t flat_end, F&& visit)
requires (N > 1);
72 void _set_point(
size_t dim,
size_t coord);
73 void _set_span(
size_t dim,
size_t b,
size_t e);
74 void _emit_middle(
size_t dim,
size_t b,
size_t e);
78 std::array<size_t, N> _strides{};
83template <
typename T,
size_t N>
84IndexRangesPartitioner<T, N>::IndexRangesPartitioner(
const R& ranges) : _ranges(ranges) {
87 std::array<size_t, N> extent{};
88 for (
size_t d = 0; d < N; ++d) {
89 extent[d] = _ranges.size(d);
95 if (_active_rank > 0) {
96 _strides[_active_rank - 1] = 1;
97 for (
size_t d = _active_rank - 1; d-- > 0; ) {
98 _strides[d] = _strides[d + 1] * extent[d + 1];
101 for (
size_t d = _active_rank; d < N; ++d) {
102 _box.dim(d) = _ranges.dim(d);
107template <
typename T,
size_t N>
108size_t IndexRangesPartitioner<T, N>::size()
const {
109 return _ranges.size();
113template <
typename T,
size_t N>
114size_t IndexRangesPartitioner<T, N>::active_rank()
const {
119template <
typename T,
size_t N>
121bool IndexRangesPartitioner<T, N>::for_each_box(
size_t flat_beg,
size_t flat_end, F&& visit)
123 if (_active_rank == 0) {
126 _box = _ranges.unravel(flat_beg, flat_end);
127 if constexpr (std::is_same_v<std::invoke_result_t<F, R>,
bool>) {
136template <
typename T,
size_t N>
138bool IndexRangesPartitioner<T, N>::for_each_box(
size_t flat_beg,
size_t flat_end, F&& visit)
141 if (_active_rank == 0) {
149 auto recurse = [&](
auto& self,
size_t dim,
size_t b,
size_t e) ->
bool {
154 if (dim == _active_rank - 1) {
155 _set_span(dim, b, e);
156 if constexpr (std::is_same_v<std::invoke_result_t<F, R>,
bool>) {
164 size_t s = _strides[dim];
165 size_t outer_b = b / s, inner_b = b % s;
166 size_t outer_e = e / s, inner_e = e % s;
168 if (outer_b == outer_e) {
169 _set_point(dim, outer_b);
170 return self(self, dim + 1, inner_b, inner_e);
173 _set_point(dim, outer_b);
174 if (self(self, dim + 1, inner_b, s)) {
179 if (outer_b < outer_e) {
180 _emit_middle(dim, outer_b, outer_e);
181 if constexpr (std::is_same_v<std::invoke_result_t<F, R>,
bool>) {
190 _set_point(dim, outer_e);
191 if (self(self, dim + 1, 0, inner_e)) {
198 return recurse(recurse, 0, flat_beg, flat_end);
202template <
typename T,
size_t N>
203void IndexRangesPartitioner<T, N>::_set_point(
size_t dim,
size_t coord) {
204 auto [bd, ed, sd] = _ranges.dim(dim);
205 _box.dim(dim) = {
static_cast<T
>(bd + coord * sd),
static_cast<T
>(bd + (coord + 1) * sd), sd };
209template <
typename T,
size_t N>
210void IndexRangesPartitioner<T, N>::_set_span(
size_t dim,
size_t b,
size_t e) {
211 auto [bd, ed, sd] = _ranges.dim(dim);
212 _box.dim(dim) = {
static_cast<T
>(bd + b * sd),
static_cast<T
>(bd + e * sd), sd };
216template <
typename T,
size_t N>
217void IndexRangesPartitioner<T, N>::_emit_middle(
size_t dim,
size_t b,
size_t e) {
218 _set_span(dim, b, e);
219 for (
size_t d = dim + 1; d < _active_rank; ++d) {
220 _box.dim(d) = _ranges.dim(d);
293template <
typename C = DefaultClosureWrapper>
346 template <
typename F>
352 template <
typename F>
353 decltype(
auto)
operator () (F&& callable);
360 size_t _chunk_size{0};
395 return _closure_wrapper;
401 return _closure_wrapper;
408 _closure_wrapper = std::forward<F>(fn);
416 return std::forward<F>(callable);
420 return [
this, c=std::forward<F>(callable)]()
mutable { _closure_wrapper(c); };
475template <
typename C = DefaultClosureWrapper>
516 template <
typename F>
517 void loop(
size_t N,
size_t W,
size_t curr_b,
size_t chunk_size, F&& func);
535 template <IndexRangesLike R,
typename F>
536 void loop(
const R& range,
size_t N,
size_t W,
size_t curr_b,
size_t chunk_size, F&& func)
const;
554 return this->_chunk_size ? this->_chunk_size : N/W + (w < N%W);
560void StaticPartitioner<C>::loop(
size_t N,
size_t W,
size_t curr_b,
size_t chunk_size, F&& func) {
561 size_t stride = W * chunk_size;
563 size_t curr_e = (std::min)(curr_b + chunk_size, N);
564 if constexpr (std::is_same_v<std::invoke_result_t<F, size_t, size_t>,
bool>) {
565 if(func(curr_b, curr_e)) {
569 func(curr_b, curr_e);
577template <IndexRangesLike R,
typename F>
578void StaticPartitioner<C>::loop(
579 const R& range,
size_t N,
size_t W,
size_t curr_b,
size_t chunk_size, F&& func
581 IndexRangesPartitioner irp(range);
582 size_t stride = W * chunk_size;
584 size_t curr_e = (std::min)(curr_b + chunk_size, N);
585 if(irp.for_each_box(curr_b, curr_e, func)) {
635template <
typename C = DefaultClosureWrapper>
668 template <
typename F>
669 void loop(
size_t N,
size_t W, std::atomic<size_t>& next, F&& func)
const;
674 template <IndexRangesLike R,
typename F>
675 void loop(
const R& range,
size_t N,
size_t W, std::atomic<size_t>& next, F&& func)
const;
693void GuidedPartitioner<C>::loop(
694 size_t N,
size_t W, std::atomic<size_t>& next, F&& func
697 size_t chunk_size = (this->_chunk_size == 0) ?
size_t{1} : this->_chunk_size;
698 size_t p1 = 2 * W * (chunk_size + 1);
699 float p2 = 0.5f /
static_cast<float>(W);
700 size_t curr_b = next.load(std::memory_order_relaxed);
703 size_t r = N - curr_b;
704 size_t csize = (r < p1) ? chunk_size : (std::max)(static_cast<size_t>(p2 * r), chunk_size);
705 size_t curr_e = (std::min)(curr_b + csize, N);
706 if(next.compare_exchange_weak(curr_b, curr_e,
707 std::memory_order_relaxed,
708 std::memory_order_relaxed)) {
709 if constexpr (std::is_same_v<std::invoke_result_t<F, size_t, size_t>,
bool>) {
710 if(func(curr_b, curr_e)) {
714 func(curr_b, curr_e);
723template <IndexRangesLike R,
typename F>
724void GuidedPartitioner<C>::loop(
725 const R& range,
size_t N,
size_t W, std::atomic<size_t>& next, F&& func
728 IndexRangesPartitioner irp(range);
730 size_t chunk_size = (this->_chunk_size == 0) ?
size_t{1} : this->_chunk_size;
731 size_t p1 = 2 * W * (chunk_size + 1);
732 float p2 = 0.5f /
static_cast<float>(W);
733 size_t curr_b = next.load(std::memory_order_relaxed);
736 size_t r = N - curr_b;
737 size_t csize = (r < p1) ? chunk_size : (std::max)(static_cast<size_t>(p2 * r), chunk_size);
738 size_t curr_e = (std::min)(curr_b + csize, N);
739 if(next.compare_exchange_weak(curr_b, curr_e,
740 std::memory_order_relaxed,
741 std::memory_order_relaxed)) {
742 if(irp.for_each_box(curr_b, curr_e, func)) {
793template <
typename C = DefaultClosureWrapper>
825 template <
typename F>
826 void loop(
size_t N,
size_t, std::atomic<size_t>& next, F&& func)
const;
831 template <IndexRangesLike R,
typename F>
832 void loop(
const R& range,
size_t N,
size_t, std::atomic<size_t>& next, F&& func)
const;
850void DynamicPartitioner<C>::loop(
size_t N,
size_t, std::atomic<size_t>& next, F&& func)
const {
852 size_t chunk_size = (this->_chunk_size == 0) ?
size_t{1} : this->_chunk_size;
853 size_t curr_b = next.fetch_add(chunk_size, std::memory_order_relaxed);
856 if constexpr (std::is_same_v<std::invoke_result_t<F, size_t, size_t>,
bool>) {
857 if(func(curr_b, (std::min)(curr_b + chunk_size, N))) {
861 func(curr_b, (std::min)(curr_b + chunk_size, N));
863 curr_b = next.fetch_add(chunk_size, std::memory_order_relaxed);
869template <IndexRangesLike R,
typename F>
870void DynamicPartitioner<C>::loop(
871 const R& range,
size_t N,
size_t, std::atomic<size_t>& next, F&& func
874 IndexRangesPartitioner irp(range);
876 size_t curr_b = next.load(std::memory_order_relaxed);
877 size_t chunk_size = (this->_chunk_size == 0) ?
size_t{1} : this->_chunk_size;
881 size_t curr_e = (std::min)(curr_b + chunk_size, N);
882 if(next.compare_exchange_weak(curr_b, curr_e,
883 std::memory_order_relaxed,
884 std::memory_order_relaxed)) {
885 if(irp.for_each_box(curr_b, curr_e, func)) {
936template <
typename C = DefaultClosureWrapper>
996 template <
typename F>
997 void loop(
size_t N,
size_t W, std::atomic<size_t>& next, F&& func)
const;
1002 template <IndexRangesLike R,
typename F>
1003 void loop(
const R& range,
size_t N,
size_t W, std::atomic<size_t>& next, F&& func)
const;
1007 float _alpha {0.01f};
1008 float _beta {0.50f};
1012template <
typename C>
1017template <
typename C>
1023template <
typename C>
1028template <
typename C>
1035template <
typename C>
1041template <
typename C>
1047template <
typename C>
1050 size_t b1 =
static_cast<size_t>(_alpha * N * W);
1051 size_t b2 =
static_cast<size_t>(_beta * N * W);
1057 b1 = (std::max)(b1,
size_t{1});
1058 b2 = (std::max)(b2, b1 + 1);
1064template <
typename C>
1065template <
typename F>
1066void RandomPartitioner<C>::loop(
1067 size_t N,
size_t W, std::atomic<size_t>& next, F&& func
1070 auto [b1, b2] = chunk_size_range(N, W);
1072 std::default_random_engine engine {std::random_device{}()};
1073 std::uniform_int_distribution<size_t> dist(b1, b2);
1075 size_t chunk_size = dist(engine);
1076 size_t curr_b = next.fetch_add(chunk_size, std::memory_order_relaxed);
1079 if constexpr (std::is_same_v<std::invoke_result_t<F, size_t, size_t>,
bool>) {
1080 if(func(curr_b, (std::min)(curr_b + chunk_size, N))) {
1084 func(curr_b, (std::min)(curr_b + chunk_size, N));
1086 chunk_size = dist(engine);
1087 curr_b = next.fetch_add(chunk_size, std::memory_order_relaxed);
1092template <
typename C>
1093template <IndexRangesLike R,
typename F>
1094void RandomPartitioner<C>::loop(
1095 const R& range,
size_t N,
size_t W, std::atomic<size_t>& next, F&& func
1098 IndexRangesPartitioner irp(range);
1100 auto [b1, b2] = chunk_size_range(N, W);
1102 std::default_random_engine engine{std::random_device{}()};
1103 std::uniform_int_distribution<size_t> dist(b1, b2);
1105 size_t curr_b = next.load(std::memory_order_relaxed);
1108 size_t curr_e = (std::min)(curr_b + dist(engine), N);
1109 if(next.compare_exchange_weak(curr_b, curr_e,
1110 std::memory_order_relaxed,
1111 std::memory_order_relaxed)) {
1112 if(irp.for_each_box(curr_b, curr_e, func)) {
1137template <
typename P>
1138concept PartitionerLike = std::derived_from<P, PartitionerBase<typename P::closure_wrapper_type>>;
1147template <
typename P>
class to create a default closure wrapper
Definition partitioner.hpp:31
DynamicPartitioner()=default
default constructor
static constexpr PartitionerType type()
queries the partition type (dynamic)
Definition partitioner.hpp:801
class to create a guided partitioner for scheduling parallel algorithms
Definition partitioner.hpp:636
GuidedPartitioner()=default
default constructor
static constexpr PartitionerType type()
queries the partition type (dynamic)
Definition partitioner.hpp:643
class to create an N-dimensional index range of integral indices
Definition iterator.hpp:188
PartitionerBase(size_t chunk_size)
construct a partitioner with the given chunk size
Definition partitioner.hpp:370
static constexpr bool is_default_wrapper_v
indicating if the given closure wrapper is a default wrapper (i.e., empty)
Definition partitioner.hpp:301
C closure_wrapper_type
the closure type
Definition partitioner.hpp:306
void chunk_size(size_t cz)
update the chunk size of this partitioner
Definition partitioner.hpp:388
const DefaultClosureWrapper & closure_wrapper() const
Definition partitioner.hpp:394
void closure_wrapper(F &&fn)
modify the closure wrapper object
Definition partitioner.hpp:407
PartitionerBase(size_t chunk_size, C &&closure_wrapper)
construct a partitioner with the given chunk size and closure wrapper
Definition partitioner.hpp:375
C & closure_wrapper()
acquire a mutable access to the closure wrapper object
Definition partitioner.hpp:400
PartitionerBase()=default
default constructor
size_t chunk_size() const
Definition partitioner.hpp:382
std::pair< size_t, size_t > chunk_size_range(size_t N, size_t W) const
queries the range of chunk size
Definition partitioner.hpp:1048
RandomPartitioner()=default
default constructor
static constexpr PartitionerType type()
queries the partition type (dynamic)
Definition partitioner.hpp:944
float alpha() const
queries the alpha value
Definition partitioner.hpp:1036
float beta() const
queries the beta value
Definition partitioner.hpp:1042
StaticPartitioner()=default
default constructor
size_t adjusted_chunk_size(size_t N, size_t W, size_t w) const
queries the adjusted chunk size
Definition partitioner.hpp:553
static constexpr PartitionerType type()
queries the partition type (static)
Definition partitioner.hpp:483
concept to check if a type is a partitioner
Definition partitioner.hpp:1138
taskflow namespace
Definition small_vector.hpp:20
@ STATIC
static task type
Definition task.hpp:25
PartitionerType
enumeration of all partitioner types
Definition partitioner.hpp:19
@ DYNAMIC
dynamic partitioner type
Definition partitioner.hpp:23
@ STATIC
static partitioner type
Definition partitioner.hpp:21
constexpr bool is_partitioner_v
concept to check if a type is a partitioner (variable template)
Definition partitioner.hpp:1148
GuidedPartitioner<> DefaultPartitioner
default partitioner set to tf::GuidedPartitioner
Definition partitioner.hpp:1130