ttg 1.0.0
Template Task Graph (TTG): flowgraph-based programming model for high-performance distributed-memory algorithms
Loading...
Searching...
No Matches
constraint.h
Go to the documentation of this file.
1// SPDX-License-Identifier: BSD-3-Clause
2#ifndef TTG_CONSTRAINT_H
3#define TTG_CONSTRAINT_H
4
5#include <functional>
6#include <atomic>
7#include <cstdint>
8#include <mutex>
9#include <map>
10
11#include "ttg/util/span.h"
12
13#ifdef TTG_USE_BUNDLED_BOOST_CALLABLE_TRAITS
15#else
17#endif
18
19namespace ttg {
20
21 template<typename Key>
23 using key_type = Key;
24 using listener_t = std::function<void(const ttg::span<key_type>&)>;
25
28
30 : m_listeners(std::move(cb.m_listeners))
31 { }
32
34 : m_listeners(cb.m_listeners)
35 {}
36
38 m_listeners = std::move(cb.m_listeners);
39 }
41 m_listeners = cb.m_listeners;
42 }
43
44 virtual ~ConstraintBase() = default;
45
47 auto g = this->lock_guard();
48 m_listeners.insert_or_assign(tt, std::move(l));
49 }
50
51 void notify_listener(const ttg::span<key_type>& keys, ttg::TTBase* tt) {
52 auto& release = m_listeners[tt];
53 release(keys);
54 }
55
56 protected:
57
58 auto lock_guard() {
59 return std::lock_guard{m_mtx};
60 }
61
62 private:
63 std::map<ttg::TTBase*, listener_t> m_listeners;
64 std::mutex m_mtx;
65 };
66
67 template<typename Key,
68 typename Ordinal = std::size_t,
69 typename Compare = std::less<Ordinal>,
70 typename Mapper = ttg::Void>
72
73 using key_type = std::conditional_t<ttg::meta::is_void_v<Key>, ttg::Void, Key>;
74 using ordinal_type = Ordinal;
75 using keymap_t = std::function<Ordinal(const key_type&)>;
76 using compare_t = Compare;
78
79 static_assert((!Compare{}(Ordinal{}, Ordinal{})), "Comparator must provide strict ordering.");
80
81 protected:
83 std::map<ttg::TTBase*, std::vector<key_type>> m_keys;
84
85 sequence_elem_t() = default;
90
91 void add_key(const key_type& key, ttg::TTBase* tt) {
92 auto it = m_keys.find(tt);
93 if (it == m_keys.end()) {
94 m_keys.insert(std::make_pair(tt, std::vector<key_type>{key}));
95 } else {
96 it->second.push_back(key);
97 }
98 }
99 };
100
101 bool comp_equal(const Ordinal& a, const Ordinal& b) const {
102 return (!m_order(a, b) && !m_order(b, a));
103 }
104
105 bool eligible(const Ordinal& ord) const {
106 return m_order(ord, m_current) || comp_equal(ord, m_current);
107 }
108
109 bool check_key_impl(const key_type& key, Ordinal ord, ttg::TTBase *tt) {
110 if (!m_stopped) {
111 if (eligible(ord)) {
112 // key should be executed
113 if (m_auto_release) { // only needed for auto-release
114 m_active.fetch_add(1, std::memory_order_relaxed);
115 // revert the current ordinal to the lower ordinal
116 m_current = ord;
117 }
118 return true;
119 } else if (m_sequence.empty() && m_auto_release && 0 == m_active.load(std::memory_order_relaxed)) {
120 // there are no keys (active or blocked) so we execute to avoid a deadlock
121 // we don't change the current ordinal because there may be lower ordinals coming in later
122 // NOTE: there is a race condition between the check here and the increment above.
123 // This is mostly benign as it can lead to out-of-sequence released tasks.
124 // Avoiding this race would incur significant overheads.
125 m_active.fetch_add(1, std::memory_order_relaxed);
126 return true;
127 }
128 }
129 // key should be deferred
130 auto g = this->lock_guard();
131 if (!m_stopped && eligible(ord)) {
132 // someone released this ordinal while we took the lock
133 return true;
134 }
135 auto it = m_sequence.find(ord);
136 if (it == m_sequence.end()) {
137 auto [iter, success] = m_sequence.insert(std::make_pair(ord, sequence_elem_t{}));
138 assert(success);
139 it = iter;
140 }
141 it->second.add_key(key, tt);
142 return false;
143 }
144
145
147 if (m_auto_release) {
148 auto active = m_active.fetch_sub(1, std::memory_order_relaxed) - 1;
149 if (0 == active) {
150 release_next();
151 }
152 }
153 }
154
155 // used in the auto case
157 if (this->m_stopped) {
158 // don't release tasks if we're stopped
159 return;
160 }
161 // trigger the next sequence
162 sequence_elem_t elem;
163 {
164 // extract the next sequence
165 auto g = this->lock_guard();
166 auto it = this->m_sequence.begin(); // sequence is ordered by ordinal
167 if (it == this->m_sequence.end()) {
168 return; // nothing to be done
169 }
170 this->m_current = it->first;
171 elem = std::move(it->second);
172 this->m_sequence.erase(it);
173 }
174
175 for (auto& seq : elem.m_keys) {
176 // account for the newly active keys
177 this->m_active.fetch_add(seq.second.size(), std::memory_order_relaxed);
178 this->notify_listener(ttg::span<key_type>(seq.second.data(), seq.second.size()), seq.first);
179 }
180 }
181
182
183 // used in the non-auto case
184 void release_next(ordinal_type ord, bool force_check = false) {
185 if (this->m_stopped) {
186 // don't release tasks if we're stopped but remember that this was released
187 this->m_current = ord;
188 return;
189 }
190 if (!force_check && eligible(ord)) {
191 return; // already at the provided ordinal, nothing to be done
192 }
193 // trigger the next sequence(s) (m_sequence is ordered by ordinal)
194 std::vector<sequence_elem_t> seqs;
195 {
196 auto g = this->lock_guard();
197 // set current ordinal
198 this->m_current = ord;
199 for (auto it = this->m_sequence.begin(); it != this->m_sequence.end();) {
200 if (!eligible(it->first)) break;
201 // extract the next sequence
202 this->m_current = it->first;
203 seqs.push_back(std::move(it->second));
204 it = this->m_sequence.erase(it);
205 }
206 }
207 for (auto& elem : seqs) {
208 for (auto& e : elem.m_keys) {
209 // account for the newly active keys
210 this->notify_listener(ttg::span<key_type>(e.second.data(), e.second.size()), e.first);
211 }
212 }
213 }
214
215 public:
216
220 SequencedKeysConstraint(bool auto_release = false)
221 : base_t()
222 , m_auto_release(auto_release)
223 { }
224
225 template<typename Mapper_>
226 requires(std::is_invocable_v<Mapper_, Key>)
227 SequencedKeysConstraint(Mapper_&& map, bool auto_release = false)
228 : base_t()
229 , m_map(std::forward<Mapper_>(map))
230 , m_auto_release(auto_release)
231 { }
232
234
236
238
240
241 virtual ~SequencedKeysConstraint() = default;
242
243 /* Check whether the key may be executed.
244 * Returns true if the key may be executed.
245 * Otherwise, returns false and */
246 template<typename Key_ = key_type, typename Mapper_ = Mapper>
247 std::enable_if_t<!ttg::meta::is_void_v<Key_> && !ttg::meta::is_void_v<Mapper_>, bool>
248 check(const key_type& key, ttg::TTBase *tt) {
249 ordinal_type ord = m_map(key);
250 return this->check_key_impl(key, ord, tt);
251 }
252
253 template<typename Key_ = key_type, typename Mapper_ = Mapper>
254 std::enable_if_t<!ttg::meta::is_void_v<Key_> && ttg::meta::is_void_v<Mapper_>, bool>
255 check(const key_type& key, Ordinal ord, ttg::TTBase *tt) {
256 return this->check_key_impl(key, ord, tt);
257 }
258
259 template<typename Key_ = key_type, typename Mapper_ = Mapper>
260 std::enable_if_t<ttg::meta::is_void_v<Key_> && !ttg::meta::is_void_v<Mapper_>, bool>
262 return this->check_key_impl(ttg::Void{}, m_map(), tt);
263 }
264
265 template<typename Key_ = key_type, typename Mapper_ = Mapper>
266 std::enable_if_t<ttg::meta::is_void_v<Key_> && ttg::meta::is_void_v<Mapper_>, bool>
268 return this->check_key_impl(ttg::Void{}, ord, tt);
269 }
270
271 template<typename Key_ = key_type, typename Mapper_ = Mapper>
272 std::enable_if_t<!ttg::meta::is_void_v<Key_> && !ttg::meta::is_void_v<Mapper_>>
273 complete(const key_type& key, ttg::TTBase *tt) {
274 this->complete_key_impl();
275 }
276
277 template<typename Key_ = key_type, typename Mapper_ = Mapper>
278 std::enable_if_t<!ttg::meta::is_void_v<Key_> && ttg::meta::is_void_v<Mapper_>>
279 complete(const key_type& key, Ordinal ord, ttg::TTBase *tt) {
280 this->complete_key_impl();
281 }
282
283 template<typename Key_ = key_type, typename Mapper_ = Mapper>
284 std::enable_if_t<!ttg::meta::is_void_v<Key_> && ttg::meta::is_void_v<Mapper_>>
285 complete(Ordinal ord, ttg::TTBase *tt) {
286 this->complete_key_impl();
287 }
288
289 template<typename Key_ = key_type, typename Mapper_ = Mapper>
290 std::enable_if_t<!ttg::meta::is_void_v<Key_> && !ttg::meta::is_void_v<Mapper_>>
292 this->complete_key_impl();
293 }
294
300 void stop() {
301 m_stopped = true;
302 }
303
309 void start() {
310 if (m_stopped) {
311 m_stopped = false;
312 if (m_auto_release) {
313 release_next();
314 } else {
315 auto ord = m_current;
316 // release the first set of available keys if none were set explicitly
317 if (ord == std::numeric_limits<ordinal_type>::min() &&
318 this->m_sequence.begin() != this->m_sequence.end()) {
319 ord = this->m_sequence.begin()->first;
320 }
321 release_next(ord, true); // force the check for a next release even if the current ordinal hasn't changed
322 }
323 }
324 }
325
329 void release(ordinal_type ord = 0) {
330 if (m_auto_release) {
331 // last key for this ordinal, release the next
332 // the provided ordinal is ignored
333 release_next();
334 } else {
335 release_next(ord);
336 }
337 }
338
339 bool is_auto() const {
340 return m_auto_release;
341 }
342
343
344 protected:
345 std::map<ordinal_type, sequence_elem_t, compare_t> m_sequence;
346 ordinal_type m_current = std::numeric_limits<ordinal_type>::min();
347 [[no_unique_address]]
348 Mapper m_map;
349 [[no_unique_address]]
351 std::atomic<std::size_t> m_active;
352 bool m_stopped = false;
353 bool m_auto_release = false;
354 };
355
356 // deduction guides: take type of first argument to Mapper as the key type
357 // TODO: can we use the TTG callable_args instead?
358 template<typename Mapper, typename = std::enable_if_t<std::is_invocable_v<Mapper, std::decay_t<std::tuple_element_t<0, boost::callable_traits::args_t<Mapper>>>>>>
361 std::decay_t<std::tuple_element_t<0, boost::callable_traits::args_t<Mapper>>>,
362 std::decay_t<boost::callable_traits::return_type_t<Mapper>>,
363 std::less<std::decay_t<boost::callable_traits::return_type_t<Mapper>>>,
364 std::enable_if_t<std::is_invocable_v<Mapper, std::decay_t<std::tuple_element_t<0, boost::callable_traits::args_t<Mapper>>>>, Mapper>
365 >;
366
367 template<typename Mapper, typename = std::enable_if_t<std::is_invocable_v<Mapper, std::decay_t<std::tuple_element_t<0, boost::callable_traits::args_t<Mapper>>>>>>
370 std::decay_t<std::tuple_element_t<0, boost::callable_traits::args_t<Mapper>>>,
371 std::decay_t<boost::callable_traits::return_type_t<Mapper>>,
372 std::less<std::decay_t<boost::callable_traits::return_type_t<Mapper>>>,
373 std::enable_if_t<std::is_invocable_v<Mapper, std::decay_t<std::tuple_element_t<0, boost::callable_traits::args_t<Mapper>>>>, Mapper>
374 >;
375
376 template<typename Key, typename Ordinal, typename Compare, typename Mapper>
379
380 template<typename Key, typename Ordinal, typename Compare, typename Mapper>
383
398 template<template<typename...> typename Constraint, typename... Args>
399 auto make_shared_constraint(Args&&... args) {
400 return std::make_shared<decltype(Constraint(std::forward<Args>(args)...))>(std::forward<Args>(args)...);
401 }
402
407 template<typename Constraint, typename... Args>
408 auto make_shared_constraint(Args&&... args) {
409 return std::make_shared<Constraint>(std::forward<Args>(args)...);
410 }
411
412
413
414} // namespace ttg
415
416#endif // TTG_CONSTRAINT_H
A base class for all template tasks.
Definition tt.h:32
A complete version of void.
Definition void.h:12
STL namespace.
constexpr auto get(typelist< T, RestOfTs... >)
Definition typelist.h:102
top-level TTG namespace contains runtime-neutral functionality
Definition keymap.h:9
auto make_shared_constraint(Args &&... args)
Definition constraint.h:399
void notify_listener(const ttg::span< key_type > &keys, ttg::TTBase *tt)
Definition constraint.h:51
ConstraintBase(ConstraintBase &&cb)
Definition constraint.h:29
virtual ~ConstraintBase()=default
ConstraintBase & operator=(const ConstraintBase &cb)
Definition constraint.h:40
ConstraintBase(const ConstraintBase &cb)
Definition constraint.h:33
std::function< void(const ttg::span< key_type > &)> listener_t
Definition constraint.h:24
void add_listener(listener_t l, ttg::TTBase *tt)
Definition constraint.h:46
ConstraintBase & operator=(ConstraintBase &&cb)
Definition constraint.h:37
std::map< ttg::TTBase *, std::vector< key_type > > m_keys
Definition constraint.h:83
sequence_elem_t(sequence_elem_t &&)=default
sequence_elem_t(const sequence_elem_t &)=default
void add_key(const key_type &key, ttg::TTBase *tt)
Definition constraint.h:91
sequence_elem_t & operator=(const sequence_elem_t &)=default
sequence_elem_t & operator=(sequence_elem_t &&)=default
std::enable_if_t<!ttg::meta::is_void_v< Key_ > &&!ttg::meta::is_void_v< Mapper_ > > complete(const key_type &key, ttg::TTBase *tt)
Definition constraint.h:273
SequencedKeysConstraint(bool auto_release=false)
Definition constraint.h:220
std::enable_if_t<!ttg::meta::is_void_v< Key_ > &&ttg::meta::is_void_v< Mapper_ > > complete(Ordinal ord, ttg::TTBase *tt)
Definition constraint.h:285
std::enable_if_t< ttg::meta::is_void_v< Key_ > &&!ttg::meta::is_void_v< Mapper_ >, bool > check(ttg::TTBase *tt)
Definition constraint.h:261
SequencedKeysConstraint & operator=(SequencedKeysConstraint &&skc)=default
bool check_key_impl(const key_type &key, Ordinal ord, ttg::TTBase *tt)
Definition constraint.h:109
std::enable_if_t< ttg::meta::is_void_v< Key_ > &&ttg::meta::is_void_v< Mapper_ >, bool > check(ordinal_type ord, ttg::TTBase *tt)
Definition constraint.h:267
SequencedKeysConstraint & operator=(const SequencedKeysConstraint &skc)=default
ConstraintBase< Key > base_t
Definition constraint.h:77
std::enable_if_t<!ttg::meta::is_void_v< Key_ > &&ttg::meta::is_void_v< Mapper_ >, bool > check(const key_type &key, Ordinal ord, ttg::TTBase *tt)
Definition constraint.h:255
std::atomic< std::size_t > m_active
Definition constraint.h:351
bool comp_equal(const Ordinal &a, const Ordinal &b) const
Definition constraint.h:101
virtual ~SequencedKeysConstraint()=default
void release(ordinal_type ord=0)
Definition constraint.h:329
std::conditional_t< ttg::meta::is_void_v< Key >, ttg::Void, Key > key_type
Definition constraint.h:73
std::map< ordinal_type, sequence_elem_t, compare_t > m_sequence
Definition constraint.h:345
std::enable_if_t<!ttg::meta::is_void_v< Key_ > &&!ttg::meta::is_void_v< Mapper_ >, bool > check(const key_type &key, ttg::TTBase *tt)
Definition constraint.h:248
void release_next(ordinal_type ord, bool force_check=false)
Definition constraint.h:184
SequencedKeysConstraint(const SequencedKeysConstraint &skc)=default
std::enable_if_t<!ttg::meta::is_void_v< Key_ > &&ttg::meta::is_void_v< Mapper_ > > complete(const key_type &key, Ordinal ord, ttg::TTBase *tt)
Definition constraint.h:279
std::function< Ordinal(const key_type &)> keymap_t
Definition constraint.h:75
std::enable_if_t<!ttg::meta::is_void_v< Key_ > &&!ttg::meta::is_void_v< Mapper_ > > complete(ttg::TTBase *tt)
Definition constraint.h:291
SequencedKeysConstraint(Mapper_ &&map, bool auto_release=false)
Definition constraint.h:227
bool eligible(const Ordinal &ord) const
Definition constraint.h:105
SequencedKeysConstraint(SequencedKeysConstraint &&skc)=default