Everett
Loading...
Searching...
No Matches
rank_groups.h
Go to the documentation of this file.
1
13#pragma once
14
16
17#include <everett/rank15.h>
18
19#include <algorithm>
20#include <array>
21#include <bit>
22#include <cstdint>
23#include <limits>
24#include <span>
25#include <stdexcept>
26#include <utility>
27#include <vector>
28
29namespace everett {
30 namespace rank_groups_detail {
31 template <unsigned Bits, unsigned Word, unsigned Plane>
32 constexpr std::uint64_t plane_mask() noexcept {
33 std::uint64_t mask = 0;
34 for (unsigned bit = 0; bit < 64; ++bit)
35 if ((Word * 64 + bit) % Bits == Plane) mask |= std::uint64_t{1} << bit;
36 return mask;
37 }
38
39 // The caller supplies at most 128 classes and exactly the words containing
40 // them. Weighted bit populations preserve fields split across word cuts.
41 template <unsigned Bits> inline unsigned prefix_portable(
42 word_view words, unsigned count) noexcept {
43 static_assert(Bits == 2 || Bits == 3 || Bits == 5);
44 constexpr auto masks = [] {
45 std::array<std::array<std::uint64_t, Bits>, Bits> result{};
46 for (unsigned word = 0; word < Bits; ++word)
47 for (unsigned bit = 0; bit < 64; ++bit)
48 result[word][(word * 64 + bit) % Bits] |= std::uint64_t{1} << bit;
49 return result;
50 }();
51 unsigned result = 0;
52 std::uint64_t pairs = 0;
53 auto bits = count * Bits;
54 for (unsigned word = 0; word * 64 < bits; ++word) {
55 auto value = words[word];
56 auto remaining = bits - word * 64;
57 if (remaining < 64) value &= (std::uint64_t{1} << remaining) - 1;
58 if constexpr (Bits == 2) {
59 value = (value & 0x3333333333333333ull) + ((value >> 2) & 0x3333333333333333ull);
60 pairs += (value & 0x0f0f0f0f0f0f0f0full) + ((value >> 4) & 0x0f0f0f0f0f0f0f0full);
61 } else {
62 for (unsigned plane = 0; plane < Bits; ++plane)
63 result += unsigned(std::popcount(value & masks[word % Bits][plane])) << plane;
64 }
65 }
66 if constexpr (Bits == 2) {
67 // Four two-bit classes per byte contribute at most twelve per word;
68 // four words fit in byte lanes. Widen before summing their total.
69 pairs = (pairs & 0x00ff00ff00ff00ffull) + ((pairs >> 8) & 0x00ff00ff00ff00ffull);
70 return unsigned((pairs * 0x0001000100010001ull) >> 48);
71 }
72 return result;
73 }
74
75 template <unsigned Bits> inline unsigned prefix_portable(
76 std::uint64_t const * words, unsigned count) noexcept {
77 return prefix_portable<Bits>(word_view(std::span(words, ((count * Bits + 63) >> 6))), count);
78 }
79
80 template <unsigned Bits, simd::architecture Arch, unsigned Vector, unsigned Plane = 0>
81 simd_inline auto weighted_bits(simd::vec<std::uint8_t, 16, Arch> value) noexcept {
82 using B = simd::vec<std::uint8_t, 16, Arch>;
83 using W = simd::vec<std::uint64_t, 2, Arch>;
84 if constexpr (Bits == 2) {
85 auto pairs = (value & B(0x33)) + (value.template right<2>() & B(0x33));
86 return (pairs & B(15)) + pairs.template right<4>();
87 } else {
88 auto mask = W(plane_mask<Bits, 2 * Vector, Plane>(), plane_mask<Bits, 2 * Vector + 1, Plane>());
89 auto population = popcount(value & reinterpret_bits<std::uint8_t>(mask)).template left<Plane>();
90 if constexpr (Plane + 1 == Bits) return population;
91 else return population + weighted_bits<Bits, Arch, Vector, Plane + 1>(value);
92 }
93 }
94
95 template <unsigned Bits, simd::architecture Arch, unsigned Vector = 0>
96 simd_inline auto prefix_vectors(std::byte const * bytes, unsigned bits) noexcept {
97 using B = simd::vec<std::uint8_t, 16, Arch>;
98 using W = simd::vec<std::uint64_t, 2, Arch>;
99 auto positions = W(std::uint64_t(2 * Vector), std::uint64_t(2 * Vector + 1));
100 auto boundary = W(bits >> 6);
101 auto mask = select(positions < boundary, W(~std::uint64_t{0}),
102 select(positions == boundary, W((std::uint64_t{1} << (bits & 63)) - 1), W(0)));
103 auto value = B::loadu(reinterpret_cast<std::uint8_t const *>(bytes) + 16 * Vector) &
104 reinterpret_bits<std::uint8_t>(mask);
105 // Five-bit classes can overflow byte lanes when registers are added.
106 // Widen adjacent bytes first, then reduce only the final register.
107 auto counts = pairwise_add_widened(weighted_bits<Bits, Arch, Vector>(value));
108 if constexpr (Vector + 1 == Bits) return counts;
109 else return counts + prefix_vectors<Bits, Arch, Vector + 1>(bytes, bits);
110 }
111
112 template <unsigned Bits, simd::architecture Arch = simd::scalar>
113 simd_inline unsigned prefix(std::uint64_t const * words, unsigned count) noexcept {
114 if constexpr (std::same_as<Arch, simd::neon> && std::endian::native == std::endian::little)
115 return unsigned(reduce_add_widened(prefix_vectors<Bits, Arch>(
116 reinterpret_cast<std::byte const *>(words), count * Bits)));
117 else return prefix_portable<Bits>(words, count);
118 }
119 }
120
121 // Population classes for virtual groups of K=2^n-1 entries. A class occupies
122 // exactly n bits, including across word boundaries. One 64-bit checkpoint
123 // every 128 classes bounds general rank queries to 127 class reads. K=3,7,31
124 // reduce packed words; K=15 shares rank15's packed-word/SIMD reduction.
125 // No origin bitmap, arbitrary within-group rank, or select is kept.
126 // Views check section shapes; a reader must validate borrowed metadata.
127 template <std::uint64_t K> struct rank_groups_view {
128 static_assert(K >= 3 && K < std::numeric_limits<std::uint64_t>::max() && std::has_single_bit(K + 1),
129 "group size must be 2^n-1 and at least three");
130 static constexpr std::uint64_t group_size = K;
131 static constexpr unsigned class_bits = unsigned(std::countr_zero(K + 1));
132
133 rank_groups_view(std::span<std::uint64_t const> classes,
134 std::span<std::uint64_t const> checkpoints,
135 std::uint64_t virtual_count)
136 : rank_groups_view(word_view(classes), word_view(checkpoints), virtual_count) {}
137
138 template <class Words> requires std::is_same_v<Words, word_view>
139 rank_groups_view(Words classes, Words checkpoints,
140 std::uint64_t virtual_count)
141 : classes_(classes), checkpoints_(checkpoints), virtual_count_(virtual_count) {
142 auto groups = group_count();
143 if (groups > (std::numeric_limits<std::uint64_t>::max() - 63) / class_bits)
144 error_detail::raise<std::overflow_error>("rank groups packed size");
145 auto bits = groups * class_bits;
146 if (classes.size() != ((bits + 63) >> 6) ||
147 checkpoints.size() != ((groups + 127) >> 7))
148 error_detail::raise<std::invalid_argument>("invalid rank groups spans");
149 }
150
151 std::uint64_t size() const noexcept { return virtual_count_; }
152 // Derived from the final real group, without an endpoint entry.
153 template <simd::architecture Arch = simd::scalar>
154 std::uint64_t count() const {
155 if (!group_count()) return 0;
156 auto last = group_count() - 1;
157 auto population = class_at(last);
158 if (population > virtual_count_ - last * K)
159 error_detail::raise<std::invalid_argument>("invalid rank groups final population");
160 return add_prefix(rank<Arch>(last), population, virtual_count_);
161 }
162 std::uint64_t group_count() const noexcept {
163 return virtual_count_ / K + (virtual_count_ % K != 0);
164 }
165 word_view class_words() const noexcept { return classes_; }
166 word_view checkpoint_words() const noexcept { return checkpoints_; }
167 std::uint64_t class_at(std::uint64_t group) const {
168 if (group >= group_count()) error_detail::raise<std::out_of_range>("rank groups class");
169 return read_class(group);
170 }
171 // Exclusive prefix at K*group for an existing group only.
172 template <simd::architecture Arch = simd::scalar>
173 std::uint64_t rank(std::uint64_t group) const {
174 if (group >= group_count()) error_detail::raise<std::out_of_range>("rank groups boundary");
175 auto result = checkpoints_[group >> 7];
176 auto limit = virtual_count_;
177 if constexpr (K == 3 || K == 7 || K == 31) {
178 auto count = unsigned(group & 127);
179 if (!count) return add_prefix(result, 0, limit);
180 auto word = (group >> 7) * (2 * class_bits);
181 // The measured two-bit SWAR kernel has lower dependent latency.
182 // Wider classes retain the NEON weighted-population reduction.
183 if constexpr (K != 3 && std::same_as<Arch, simd::neon> && std::endian::native == std::endian::little)
184 if (classes_.size() - word >= 2 * class_bits)
185 return add_prefix(result, reduce_add_widened(rank_groups_detail::prefix_vectors<class_bits, Arch>(
186 classes_.bytes().data() + word * 8, count * class_bits)), limit);
187 return add_prefix(result, rank_groups_detail::prefix_portable<class_bits>(classes_.subspan(word), count), limit);
188 }
189 for (auto i = group & ~std::uint64_t{127}; i < group; ++i) result = add_prefix(result, read_class(i), limit);
190 return add_prefix(result, 0, limit);
191 }
192
193 private:
194 static std::uint64_t add_prefix(std::uint64_t checkpoint, std::uint64_t prefix, std::uint64_t limit) {
195 if (checkpoint > limit || prefix > limit - checkpoint)
196 error_detail::raise<std::invalid_argument>("invalid rank groups checkpoint or prefix");
197 return checkpoint + prefix;
198 }
199 std::uint64_t read_class(std::uint64_t group) const noexcept {
200 auto bit = group * class_bits;
201 auto word = bit >> 6;
202 unsigned shift = unsigned(bit & 63);
203 auto value = classes_[word] >> shift;
204 if (shift + class_bits > 64) value |= classes_[word + 1] << (64 - shift);
205 return value & K;
206 }
209 std::uint64_t virtual_count_;
210 };
211
212 template <> struct rank_groups_view<15> {
213 static constexpr std::uint64_t group_size = 15;
214 static constexpr unsigned class_bits = 4;
215
216 rank_groups_view(std::span<std::uint64_t const> classes,
217 std::span<std::uint64_t const> checkpoints,
218 std::uint64_t virtual_count)
219 : view_(classes, checkpoints, virtual_count) {}
220
221 template <class Words> requires std::is_same_v<Words, word_view>
222 rank_groups_view(Words classes, Words checkpoints,
223 std::uint64_t virtual_count)
224 : view_(classes, checkpoints, virtual_count) {}
225
226 word_view class_words() const noexcept { return view_.class_words(); }
227 word_view checkpoint_words() const noexcept { return view_.checkpoint_words(); }
228
229 std::uint64_t size() const noexcept { return view_.size(); }
230 template <simd::architecture Arch = simd::scalar>
231 std::uint64_t count() const { return view_.template count<Arch>(); }
232 std::uint64_t group_count() const noexcept { return view_.group_count(); }
233 std::uint64_t class_at(std::uint64_t group) const { return view_.class_at(group); }
234 template <simd::architecture Arch = simd::scalar>
235 std::uint64_t rank(std::uint64_t group) const { return view_.template rank<Arch>(group); }
236
237 private:
239 };
240
241 template <std::uint64_t K> struct rank_groups {
242 static_assert(K >= 3 && K < std::numeric_limits<std::uint64_t>::max() && std::has_single_bit(K + 1),
243 "group size must be 2^n-1 and at least three");
244 static constexpr std::uint64_t group_size = K;
245 static constexpr unsigned class_bits = unsigned(std::countr_zero(K + 1));
246
247 static rank_groups build(std::span<std::uint64_t const> source, std::uint64_t count) {
248 auto groups = count / K + (count % K != 0);
249 if (source.size() != groups) error_detail::raise<std::invalid_argument>("rank groups class length");
250 if (groups > (std::numeric_limits<std::uint64_t>::max() - 63) / class_bits)
251 error_detail::raise<std::overflow_error>("rank groups packed size");
252 auto bits = groups * class_bits;
253 rank_groups result;
254 result.virtual_count = count;
255 result.classes.resize((bits + 63) >> 6);
256 std::uint64_t total = 0;
257 for (std::uint64_t i = 0; i < groups; ++i) {
258 auto limit = i + 1 == groups && count % K ? count % K : K;
259 auto value = source[i];
260 if (value > limit) error_detail::raise<std::invalid_argument>("rank groups population");
261 if ((i & 127) == 0) result.checkpoints.push_back(total);
262 auto bit = i * class_bits;
263 unsigned shift = unsigned(bit & 63);
264 result.classes[bit >> 6] |= value << shift;
265 if (shift + class_bits > 64) result.classes[(bit >> 6) + 1] |= value >> (64 - shift);
266 total += value;
267 }
268 return result;
269 }
270
272 rank_groups_view<K> view() const && = delete;
273
274 std::vector<std::uint64_t> classes;
275 std::vector<std::uint64_t> checkpoints;
276 std::uint64_t virtual_count = 0;
277 };
278
279 // Pack complete population classes as they arrive. Only the final group may
280 // have fewer than K occurrences. Prefix population is construction scratch;
281 // the finished rank structure stores no cached total or endpoint entry.
282 // Failed allocation or validation leaves accepted classes unchanged.
283 template <std::uint64_t K> struct rank_groups_builder {
284 static constexpr auto group_size = K;
285 static constexpr auto class_bits = rank_groups<K>::class_bits;
286
291 : data_(std::move(other.data_)), groups_(other.groups_), population_(other.population_),
292 partial_(other.partial_), finished_(std::exchange(other.finished_, true)) {}
294 if (this != &other) {
295 data_ = std::move(other.data_); groups_ = other.groups_; population_ = other.population_;
296 partial_ = other.partial_; finished_ = std::exchange(other.finished_, true);
297 }
298 return *this;
299 }
300
301 std::uint64_t size() const noexcept { return data_.virtual_count; }
302 std::uint64_t group_count() const noexcept { return groups_; }
303 bool finished() const noexcept { return finished_; }
304
305 void append(std::uint64_t population, std::uint64_t width = K) {
306 require_active();
307 constexpr auto maximum = std::numeric_limits<std::uint64_t>::max();
308 if (partial_ || !width || width > K || population > width)
309 error_detail::raise<std::invalid_argument>("invalid incremental rank group");
310 if (width > maximum - data_.virtual_count || groups_ >= (maximum - 63) / class_bits)
311 error_detail::raise<std::length_error>("incremental rank groups are too large");
312 auto bits = (groups_ + 1) * class_bits;
313 auto words = (bits + 63) >> 6;
314 auto checkpoint = (groups_ & 127) == 0;
315 reserve(data_.classes, words);
316 if (checkpoint) reserve(data_.checkpoints, (groups_ >> 7) + 1);
317 // All potentially failing growth precedes the first logical mutation.
318 data_.classes.resize(static_cast<std::size_t>(words), 0);
319 if (checkpoint) data_.checkpoints.push_back(population_);
320 auto bit = groups_ * class_bits;
321 auto shift = unsigned(bit & 63);
322 data_.classes[bit >> 6] |= population << shift;
323 if (shift + class_bits > 64) data_.classes[(bit >> 6) + 1] |= population >> (64 - shift);
324 data_.virtual_count += width;
325 population_ += population;
326 ++groups_;
327 partial_ = width != K;
328 }
329
331 require_active();
332 finished_ = true;
333 return std::move(data_);
334 }
335
336 private:
338 std::uint64_t groups_ = 0, population_ = 0;
339 bool partial_ = false, finished_ = false;
340
341 void require_active() const {
342 if (finished_) error_detail::raise<std::logic_error>("incremental rank groups are finished");
343 }
344 static void reserve(std::vector<std::uint64_t> & data, std::uint64_t count) {
345 if (count <= data.capacity()) return;
346 if (count > data.max_size()) error_detail::raise<std::length_error>("incremental rank groups are too large");
347 auto capacity = data.capacity();
348 auto grown = capacity > (data.max_size() >> 1) ? data.max_size() : capacity << 1;
349 data.reserve(std::max(static_cast<std::size_t>(count), grown));
350 }
351 };
352}
Outlines exceptional check failures while preserving their types and messages.
constexpr std::uint64_t plane_mask() noexcept
Definition rank_groups.h:32
unsigned prefix(std::uint64_t const *words, unsigned count) noexcept
Definition rank_groups.h:113
unsigned prefix_portable(word_view words, unsigned count) noexcept
Definition rank_groups.h:41
auto weighted_bits(simd::vec< std::uint8_t, 16, Arch > value) noexcept
Definition rank_groups.h:81
auto prefix_vectors(std::byte const *bytes, unsigned bits) noexcept
Definition rank_groups.h:96
Definition active_engine.h:18
Declares Everett's rank15 support.
Definition rank15.h:37
Definition rank_groups.h:283
std::uint64_t group_count() const noexcept
Definition rank_groups.h:302
static void reserve(std::vector< std::uint64_t > &data, std::uint64_t count)
Definition rank_groups.h:344
rank_groups< K > data_
Definition rank_groups.h:337
rank_groups_builder & operator=(rank_groups_builder const &)=delete
rank_groups_builder & operator=(rank_groups_builder &&other) noexcept
Definition rank_groups.h:293
rank_groups< K > finish()
Definition rank_groups.h:330
rank_groups_builder(rank_groups_builder &&other) noexcept
Definition rank_groups.h:290
rank_groups_builder(rank_groups_builder const &)=delete
void require_active() const
Definition rank_groups.h:341
bool finished() const noexcept
Definition rank_groups.h:303
void append(std::uint64_t population, std::uint64_t width=K)
Definition rank_groups.h:305
std::uint64_t size() const noexcept
Definition rank_groups.h:301
rank15_view view_
Definition rank_groups.h:238
std::uint64_t size() const noexcept
Definition rank_groups.h:229
rank_groups_view(std::span< std::uint64_t const > classes, std::span< std::uint64_t const > checkpoints, std::uint64_t virtual_count)
Definition rank_groups.h:216
std::uint64_t class_at(std::uint64_t group) const
Definition rank_groups.h:233
word_view checkpoint_words() const noexcept
Definition rank_groups.h:227
std::uint64_t count() const
Definition rank_groups.h:231
rank_groups_view(Words classes, Words checkpoints, std::uint64_t virtual_count)
Definition rank_groups.h:222
word_view class_words() const noexcept
Definition rank_groups.h:226
std::uint64_t group_count() const noexcept
Definition rank_groups.h:232
std::uint64_t rank(std::uint64_t group) const
Definition rank_groups.h:235
Definition rank_groups.h:127
static std::uint64_t add_prefix(std::uint64_t checkpoint, std::uint64_t prefix, std::uint64_t limit)
Definition rank_groups.h:194
word_view checkpoint_words() const noexcept
Definition rank_groups.h:166
rank_groups_view(Words classes, Words checkpoints, std::uint64_t virtual_count)
Definition rank_groups.h:139
static constexpr unsigned class_bits
Definition rank_groups.h:131
std::uint64_t group_count() const noexcept
Definition rank_groups.h:162
std::uint64_t class_at(std::uint64_t group) const
Definition rank_groups.h:167
std::uint64_t read_class(std::uint64_t group) const noexcept
Definition rank_groups.h:199
rank_groups_view(std::span< std::uint64_t const > classes, std::span< std::uint64_t const > checkpoints, std::uint64_t virtual_count)
Definition rank_groups.h:133
static constexpr std::uint64_t group_size
Definition rank_groups.h:130
word_view classes_
Definition rank_groups.h:207
std::uint64_t virtual_count_
Definition rank_groups.h:209
word_view checkpoints_
Definition rank_groups.h:208
std::uint64_t rank(std::uint64_t group) const
Definition rank_groups.h:173
std::uint64_t count() const
Definition rank_groups.h:154
std::uint64_t size() const noexcept
Definition rank_groups.h:151
word_view class_words() const noexcept
Definition rank_groups.h:165
Definition rank_groups.h:241
static rank_groups build(std::span< std::uint64_t const > source, std::uint64_t count)
Definition rank_groups.h:247
rank_groups_view< K > view() const &&=delete
static constexpr std::uint64_t group_size
Definition rank_groups.h:244
rank_groups_view< K > view() const &
Definition rank_groups.h:271
std::uint64_t virtual_count
Definition rank_groups.h:276
std::vector< std::uint64_t > checkpoints
Definition rank_groups.h:275
std::vector< std::uint64_t > classes
Definition rank_groups.h:274
static constexpr unsigned class_bits
Definition rank_groups.h:245
Definition word_view.h:31
word_view subspan(std::size_t offset, std::size_t count=std::dynamic_extent) const
Definition word_view.h:45
std::size_t size() const noexcept
Definition word_view.h:41
std::span< std::byte const > bytes() const noexcept
Definition word_view.h:43