Skip to content

Commit 9f5916d

Browse files
committed
Introduce aggregator arguments
Aggregators help pass multiple references to arguments to the task. The app controls the upper bound and chunk size. Signed-off-by: Joseph Schuchart <joseph.schuchart@stonybrook.edu>
1 parent ee6cef8 commit 9f5916d

4 files changed

Lines changed: 509 additions & 20 deletions

File tree

ttg/ttg/aggregator.h

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
#ifndef TTG_AGGREGATOR_H
3+
#define TTG_AGGREGATOR_H
4+
5+
#include <limits>
6+
#include <vector>
7+
#include "ttg/fwd.h"
8+
#include "ttg/edge.h"
9+
#include "ttg/util/meta.h"
10+
#include "ttg/terminal.h"
11+
12+
namespace ttg {
13+
14+
/// @brief Collects a variable number of values sent to the same task invocation.
15+
///
16+
/// An Aggregator<T> is the value type of an input generated by ttg::make_aggregator();
17+
/// it holds references to (and, indirectly, keeps alive) every value collected for one
18+
/// task firing. Aggregators are always const: values are exposed for reading only.
19+
template <typename ValueT>
20+
struct Aggregator {
21+
template <typename keyT, typename output_terminalsT, typename derivedT, typename input_valueTs, ttg::ExecutionSpace Space>
22+
friend class TTG_IMPL_NS::TT;
23+
24+
using decay_value_type = std::decay_t<ValueT>;
25+
26+
// try to fit the Aggregator into 2 cache lines before falling back to a heap vector
27+
static constexpr size_t short_vector_size = 6;
28+
29+
public:
30+
using value_type = std::add_const_t<decay_value_type>;
31+
32+
private:
33+
struct vector_element_t {
34+
value_type *value; // pointer to the value
35+
void *ptr; // pointer to implementation-specific data (the owning data copy)
36+
vector_element_t() = default;
37+
vector_element_t(value_type *value, void *ptr) : value(value), ptr(ptr) {}
38+
};
39+
using vector_t = typename std::vector<vector_element_t>;
40+
41+
template <typename VectorElementT>
42+
struct Iterator {
43+
private:
44+
VectorElementT *m_ptr = nullptr;
45+
46+
public:
47+
using value_type = std::add_const_t<decay_value_type>;
48+
using reference = std::add_lvalue_reference_t<value_type>;
49+
using pointer = std::add_pointer_t<value_type>;
50+
using difference_type = std::ptrdiff_t;
51+
using iterator_category = std::forward_iterator_tag;
52+
53+
Iterator() = default;
54+
template <typename Ptr>
55+
Iterator(Ptr ptr) : m_ptr(&(*ptr)) {}
56+
57+
reference operator*() const { return *m_ptr->value; }
58+
pointer operator->() const { return m_ptr->value; }
59+
60+
Iterator &operator++() {
61+
++m_ptr;
62+
return *this;
63+
}
64+
Iterator operator++(int) {
65+
Iterator tmp = *this;
66+
++(*this);
67+
return tmp;
68+
}
69+
70+
friend bool operator==(const Iterator &a, const Iterator &b) { return a.m_ptr == b.m_ptr; }
71+
friend bool operator!=(const Iterator &a, const Iterator &b) { return a.m_ptr != b.m_ptr; }
72+
};
73+
74+
public:
75+
using iterator = Iterator<const vector_element_t>;
76+
using const_iterator = Iterator<const vector_element_t>;
77+
using size_type = typename vector_t::size_type;
78+
using pointer = value_type *;
79+
using reference = std::add_lvalue_reference_t<value_type>;
80+
using const_reference = std::add_const_t<reference>;
81+
static constexpr const size_type undef_target = std::numeric_limits<size_type>::max();
82+
83+
Aggregator() = default;
84+
85+
Aggregator(size_type target) : m_target(target) {
86+
if (target > short_vector_size) {
87+
m_vec.reserve(target);
88+
m_is_dynamic = true;
89+
} else {
90+
m_is_dynamic = false;
91+
}
92+
}
93+
94+
Aggregator(const Aggregator &) = default;
95+
Aggregator(Aggregator &&) = default;
96+
Aggregator &operator=(const Aggregator &) = default;
97+
Aggregator &operator=(Aggregator &&) = default;
98+
99+
~Aggregator() = default;
100+
101+
private:
102+
/// Add an element to the aggregator; called by the runtime only.
103+
void add_value(value_type &value, void *ptr = nullptr) {
104+
if (m_is_dynamic) {
105+
m_vec.emplace_back(&value, ptr);
106+
} else {
107+
if (m_size < short_vector_size) {
108+
m_arr[m_size] = vector_element_t(&value, ptr);
109+
} else {
110+
move_to_dynamic();
111+
m_vec.emplace_back(&value, ptr);
112+
}
113+
}
114+
++m_size;
115+
}
116+
117+
bool has_target() const { return (m_target != undef_target); }
118+
119+
size_type target() const {
120+
if (m_target == undef_target) {
121+
throw std::logic_error("Aggregator has no target defined!");
122+
}
123+
return m_target;
124+
}
125+
126+
auto data() {
127+
if (m_is_dynamic) {
128+
return ttg::span(m_vec.data(), m_size);
129+
} else {
130+
return ttg::span(static_cast<vector_element_t *>(&m_arr[0]), m_size);
131+
}
132+
}
133+
134+
void move_to_dynamic() {
135+
assert(!m_is_dynamic);
136+
vector_t vec;
137+
if (has_target()) {
138+
vec.reserve(m_target);
139+
} else {
140+
vec.reserve(m_size);
141+
}
142+
vec.insert(vec.begin(), &m_arr[0], &m_arr[m_size]);
143+
m_vec = std::move(vec);
144+
m_is_dynamic = true;
145+
}
146+
147+
const vector_element_t *get_ptr() const {
148+
return (m_is_dynamic) ? m_vec.data() : static_cast<const vector_element_t *>(&m_arr[0]);
149+
}
150+
151+
public:
152+
const_reference operator[](size_type i) const { return *((m_is_dynamic) ? m_vec[i].value : m_arr[i].value); }
153+
154+
const_reference at(size_type i) const { return *((m_is_dynamic) ? m_vec.at(i).value : m_arr[i].value); }
155+
156+
size_type size() const { return m_size; }
157+
158+
const_iterator begin() const { return const_iterator(get_ptr()); }
159+
const_iterator cbegin() const { return const_iterator(get_ptr()); }
160+
const_iterator end() const { return const_iterator(get_ptr() + m_size); }
161+
const_iterator cend() const { return const_iterator(get_ptr() + m_size); }
162+
163+
private:
164+
vector_t m_vec;
165+
vector_element_t m_arr[short_vector_size];
166+
size_type m_size = 0;
167+
size_type m_target = undef_target;
168+
bool m_is_dynamic = true;
169+
};
170+
171+
namespace detail {
172+
173+
/// Trait to determine if a given type is an Aggregator
174+
template <typename T>
175+
struct is_aggregator : std::false_type {};
176+
177+
template <typename ValueT>
178+
struct is_aggregator<Aggregator<ValueT>> : std::true_type {};
179+
180+
template <typename T>
181+
constexpr bool is_aggregator_v = is_aggregator<T>::value;
182+
183+
template <typename KeyT, typename AggregatorT, typename TargetFn>
184+
struct AggregatorFactory {
185+
using aggregator_type = AggregatorT;
186+
using key_type = KeyT;
187+
188+
AggregatorFactory() : m_targetfn([]() { return aggregator_type::undef_target; }) {}
189+
190+
AggregatorFactory(TargetFn fn) : m_targetfn(std::forward<TargetFn>(fn)) {}
191+
192+
auto operator()(const key_type &key) const { return aggregator_type(m_targetfn(key)); }
193+
194+
private:
195+
TargetFn m_targetfn;
196+
};
197+
198+
struct AggregatorTargetProvider {
199+
AggregatorTargetProvider(std::size_t target = Aggregator<int>::undef_target) : m_target(target) {}
200+
201+
template <typename T>
202+
auto operator()(const T &) const {
203+
return m_target;
204+
}
205+
206+
private:
207+
std::size_t m_target;
208+
};
209+
210+
} // namespace detail
211+
212+
/// Overload of ttg::Edge for aggregated (variable-arity) inputs; created by ttg::make_aggregator.
213+
template <typename KeyT, typename ValueT>
214+
class Edge<KeyT, Aggregator<ValueT>> {
215+
public:
216+
using edge_type = ttg::Edge<KeyT, ValueT>;
217+
using aggregator_type = Aggregator<ValueT>;
218+
using aggregator_factory_type = std::function<aggregator_type(const KeyT &)>;
219+
220+
using output_terminal_type = ttg::Out<KeyT, ValueT>;
221+
using key_type = KeyT;
222+
using value_type = aggregator_type;
223+
224+
Edge(edge_type &edge)
225+
: m_edge(edge), m_aggregator_factory([](const KeyT &) { return aggregator_type(); }) {}
226+
227+
template <typename AggregatorFactory>
228+
Edge(edge_type &edge, AggregatorFactory &&aggregator_factory)
229+
: m_edge(edge), m_aggregator_factory([=](const KeyT &key) { return aggregator_factory(key); }) {}
230+
231+
/// Return reference to the underlying edge
232+
edge_type &edge() const { return m_edge; }
233+
234+
auto aggregator_factory() const { return m_aggregator_factory; }
235+
236+
/// probes if this is already has at least one input; calls the underlying edge.live()
237+
bool live() const { return m_edge.live(); }
238+
239+
/// call the underlying edge.set_in()
240+
void set_in(Out<KeyT, ValueT> *in) const { m_edge.set_in(in); }
241+
242+
/// call the underlying edge.set_out()
243+
void set_out(TerminalBase *out) const { m_edge.set_out(out); }
244+
245+
/// call the underlying edge.fire()
246+
template <typename Key = KeyT, typename Value = ValueT>
247+
std::enable_if_t<ttg::meta::is_all_void_v<Key, Value>> fire() const {
248+
m_edge.fire();
249+
}
250+
251+
private:
252+
edge_type &m_edge;
253+
aggregator_factory_type m_aggregator_factory;
254+
};
255+
256+
/// overload for remove_wrapper to expose the underlying value type
257+
namespace meta {
258+
template <typename T>
259+
struct remove_wrapper<Aggregator<T>> {
260+
using type = T;
261+
};
262+
// op() parameters are typically declared as `const ttg::Aggregator<T>&`, which retains the
263+
// top-level const after reference removal -- unwrap that form too.
264+
template <typename T>
265+
struct remove_wrapper<const Aggregator<T>> {
266+
using type = T;
267+
};
268+
} // namespace meta
269+
270+
/// @brief Turns an Edge<K,V> into an aggregating input with a per-key value count.
271+
/// @param targetfn maps a key to the total number of values expected for that key.
272+
template <typename EdgeT, typename TargetFn,
273+
typename = std::enable_if_t<std::is_invocable_v<TargetFn, const typename std::decay_t<EdgeT>::key_type>>>
274+
auto make_aggregator(EdgeT &&inedge, TargetFn &&targetfn) {
275+
using value_type = typename std::decay_t<EdgeT>::value_type;
276+
using key_type = typename std::decay_t<EdgeT>::key_type;
277+
using fact = typename detail::AggregatorFactory<key_type, Aggregator<value_type>, TargetFn>;
278+
return Edge<key_type, Aggregator<value_type>>(inedge, fact(std::forward<TargetFn>(targetfn)));
279+
}
280+
281+
/// @brief Turns an Edge<K,V> into an aggregating input with a fixed value count.
282+
template <typename EdgeT>
283+
auto make_aggregator(EdgeT &&inedge, size_t target) {
284+
return make_aggregator(inedge, typename detail::AggregatorTargetProvider(target));
285+
}
286+
287+
/// @brief Turns an Edge<K,V> into an aggregating input with an unbounded value count
288+
/// (the task fires once all producers for a key have completed; see ttg::TT::finalize_argstream).
289+
template <typename EdgeT>
290+
auto make_aggregator(EdgeT &&inedge) {
291+
using value_type = typename std::decay_t<EdgeT>::value_type;
292+
using key_type = typename std::decay_t<EdgeT>::key_type;
293+
return Edge<key_type, Aggregator<value_type>>(inedge);
294+
}
295+
296+
} // namespace ttg
297+
298+
#endif // TTG_AGGREGATOR_H

0 commit comments

Comments
 (0)