Pixie
Loading...
Searching...
No Matches
support.h
1#pragma once
2
3#include <pixie/bits.h>
4#include <pixie/detail/serialization.h>
5#include <pixie/rank_select.h>
6#include <pixie/storage/aligned.h>
7#include <pixie/storage/read_only_view.h>
8
9#include <algorithm>
10#include <array>
11#include <bit>
12#include <concepts>
13#include <cstdint>
14#include <limits>
15#include <optional>
16#include <span>
17#include <stdexcept>
18#include <string>
19
20#ifdef PIXIE_DIAGNOSTICS
21#include <spdlog/spdlog.h>
22#endif
23
24namespace pixie {
25
55template <StorageImplementation MetadataStorage = AlignedStorage>
56class RankSelectSupport
57 : public RankSelectBase<RankSelectSupport<MetadataStorage>>,
58 public SerializationBase<RankSelectSupport<MetadataStorage>> {
59 public:
63 enum class SelectSupport : uint8_t {
64 kNone = 0,
65 kSelect1 = 1,
66 kSelect0 = 2,
67 kBoth = 3,
68 };
69
70 private:
71 constexpr static size_t kWordSize = 64;
72 constexpr static size_t kSuperBlockRankIntSize = 64;
73 constexpr static size_t kBasicBlockRankIntSize = 16;
74 constexpr static size_t kBasicBlockSize = 512;
75 constexpr static size_t kWordsPerBlock = 8;
76 constexpr static size_t kSuperBlockSize = 65536;
77 constexpr static size_t kBlocksPerSuperBlock = 128;
78 constexpr static size_t kSelectSampleFrequency = 16384;
79
80 alignas(64) inline static constexpr std::array<uint64_t, 8> kDeltaSuper = [] {
81 std::array<uint64_t, 8> result{};
82 for (size_t i = 0; i < result.size(); ++i) {
83 result[i] = i * kSuperBlockSize;
84 }
85 return result;
86 }();
87 alignas(64) inline static constexpr std::array<uint16_t, 32> kDeltaBasic =
88 [] {
89 std::array<uint16_t, 32> result{};
90 for (size_t i = 0; i < result.size(); ++i) {
91 result[i] = static_cast<uint16_t>(i * kBasicBlockSize);
92 }
93 return result;
94 }();
95
96 MetadataStorage super_block_rank_; // 64-bit global prefix sums
97 MetadataStorage basic_block_rank_; // 16-bit local prefix sums
98 MetadataStorage select_samples_; // 64-bit global positions
99 ReadOnlyStorageView source_storage_;
100 std::span<const uint64_t> bits_;
101 size_t num_bits_{};
102 size_t padded_size_{};
103 size_t max_rank_{};
104 size_t select1_sample_begin_{};
105 size_t select1_sample_count_{};
106 size_t select0_sample_begin_{};
107 size_t select0_sample_count_{};
108 SelectSupport select_support_ = SelectSupport::kNone;
109 bool select0_samples_reversed_ = false;
110
111 static bool builds_select1(SelectSupport support) {
112 return (static_cast<uint8_t>(support) &
113 static_cast<uint8_t>(SelectSupport::kSelect1)) != 0;
114 }
115
116 static bool builds_select0(SelectSupport support) {
117 return (static_cast<uint8_t>(support) &
118 static_cast<uint8_t>(SelectSupport::kSelect0)) != 0;
119 }
120
121 static MetadataStorage deserialize_metadata_storage(BinaryReader& reader)
122 requires(std::same_as<MetadataStorage, AlignedStorage> ||
123 std::same_as<MetadataStorage, ReadOnlyStorageView>)
124 {
125 const std::size_t size = reader.read_size();
126 const std::span<const std::byte> bytes = reader.read_bytes(size);
127 if constexpr (std::same_as<MetadataStorage, ReadOnlyStorageView>) {
128 if (reinterpret_cast<std::uintptr_t>(bytes.data()) %
129 alignof(std::uint64_t) !=
130 0) {
131 throw std::invalid_argument(
132 "Serialized rank/select storage is not word aligned");
133 }
134 return ReadOnlyStorageView(bytes);
135 } else {
136 if (size > std::numeric_limits<std::size_t>::max() / 8) {
137 throw std::length_error("Serialized rank/select storage is too large");
138 }
139 AlignedStorage result(size * 8);
140 std::ranges::copy(bytes, result.writable_bytes().begin());
141 return result;
142 }
143 }
144
145 void validate_deserialized_state(DeserializationValidation validation) const {
146 const std::size_t required_words =
147 num_bits_ == 0 ? 0 : 1 + (num_bits_ - 1) / kWordSize;
148 if (required_words > bits_.size()) {
149 throw std::invalid_argument(
150 "RankSelectSupport source bit span is too small");
151 }
152 if (required_words > std::numeric_limits<std::size_t>::max() / kWordSize) {
153 throw std::length_error("RankSelectSupport padded size is too large");
154 }
155 if (padded_size_ != required_words * kWordSize || max_rank_ > num_bits_) {
156 throw std::invalid_argument(
157 "Invalid serialized rank/select size metadata");
158 }
159
160 const auto support_value = static_cast<std::uint8_t>(select_support_);
161 if (support_value > static_cast<std::uint8_t>(SelectSupport::kBoth) ||
162 select0_samples_reversed_) {
163 throw std::invalid_argument(
164 "Invalid serialized rank/select configuration");
165 }
166
167 const std::size_t data_superblocks = data_superblock_count();
168 const std::size_t super_entries = data_superblocks + 1;
169 const std::size_t expected_super_bytes =
170 super_entries * sizeof(std::uint64_t);
171 const std::size_t expected_basic_bytes =
172 stored_basicblock_count() * sizeof(std::uint16_t);
173 if (super_block_rank_.size_bytes() != expected_super_bytes ||
174 basic_block_rank_.size_bytes() != expected_basic_bytes ||
175 select_samples_.size_bytes() % sizeof(std::uint64_t) != 0) {
176 throw std::invalid_argument(
177 "Invalid serialized rank/select storage sizes");
178 }
179
180 const std::size_t sample_count =
181 select_samples_.size_bytes() / sizeof(std::uint64_t);
182 const auto samples_fit = [sample_count](std::size_t begin,
183 std::size_t count) {
184 return begin <= sample_count && count <= sample_count - begin;
185 };
186 if (!samples_fit(select1_sample_begin_, select1_sample_count_) ||
187 !samples_fit(select0_sample_begin_, select0_sample_count_) ||
188 (builds_select1(select_support_) != (select1_sample_count_ != 0)) ||
189 (builds_select0(select_support_) != (select0_sample_count_ != 0))) {
190 throw std::invalid_argument(
191 "Invalid serialized rank/select sample metadata");
192 }
193 if (validation == DeserializationValidation::kFull) {
194 validate_full_source_metadata();
195 return;
196 }
197
198 const auto samples = select_samples_.as_words64();
199 const auto sample_values_fit = [samples, data_superblocks](
200 std::size_t begin, std::size_t count) {
201 return std::ranges::all_of(samples.subspan(begin, count),
202 [data_superblocks](std::uint64_t sample) {
203 return data_superblocks == 0
204 ? sample == 0
205 : sample < data_superblocks;
206 });
207 };
208 if (!sample_values_fit(select1_sample_begin_, select1_sample_count_) ||
209 !sample_values_fit(select0_sample_begin_, select0_sample_count_)) {
210 throw std::invalid_argument(
211 "Serialized rank/select sample references an invalid super block");
212 }
213 }
214
215 void validate_full_source_metadata() const {
216 const auto super_blocks = super_block_rank_.as_words64();
217 const auto basic_blocks = basic_block_rank_.as_words16();
218 const auto samples = select_samples_.as_words64();
219 std::size_t next_select1 = select1_sample_begin_;
220 std::size_t next_select0 = select0_sample_begin_;
221 const std::size_t select1_end =
222 select1_sample_begin_ + select1_sample_count_;
223 const std::size_t select0_end =
224 select0_sample_begin_ + select0_sample_count_;
225
226 const auto check_initial_sample = [&](bool enabled, std::size_t& next,
227 std::size_t end) {
228 if (enabled) {
229 if (next == end || samples[next] != 0) {
230 throw std::invalid_argument("Invalid serialized rank/select samples");
231 }
232 ++next;
233 }
234 };
235 check_initial_sample(builds_select1(select_support_), next_select1,
236 select1_end);
237 check_initial_sample(builds_select0(select_support_), next_select0,
238 select0_end);
239
240 std::uint64_t rank1 = 0;
241 std::uint64_t rank0 = 0;
242 std::uint64_t super_rank = 0;
243 std::uint64_t basic_rank = 0;
244 std::uint64_t select1_milestone = kSelectSampleFrequency;
245 std::uint64_t select0_milestone = kSelectSampleFrequency;
246 const std::size_t metadata_word_count =
247 basic_blocks.size() * kWordsPerBlock;
248 for (std::size_t word_index = 0; word_index < metadata_word_count;
249 ++word_index) {
250 const std::size_t bit_position = word_index * kWordSize;
251 if (bit_position % kSuperBlockSize == 0) {
252 super_rank += basic_rank;
253 if (super_blocks[bit_position / kSuperBlockSize] != super_rank) {
254 throw std::invalid_argument(
255 "Serialized rank/select super-block ranks disagree with source");
256 }
257 basic_rank = 0;
258 }
259 if (bit_position % kBasicBlockSize == 0 &&
260 basic_blocks[bit_position / kBasicBlockSize] != basic_rank) {
261 throw std::invalid_argument(
262 "Serialized rank/select basic-block ranks disagree with source");
263 }
264
265 if (word_index >= logical_word_count()) {
266 continue;
267 }
268 const std::uint64_t word = logical_word(word_index);
269 const std::size_t word_bits = logical_word_bits(word_index);
270 const std::uint64_t ones = std::popcount(word);
271 const std::uint64_t zeros = word_bits - ones;
272 if (builds_select1(select_support_) &&
273 rank1 + ones >= select1_milestone) {
274 const std::size_t position =
275 word_index * kWordSize +
276 select_64(word, select1_milestone - rank1 - 1);
277 const std::uint64_t expected = position / kSuperBlockSize;
278 if (next_select1 == select1_end || samples[next_select1] != expected) {
279 throw std::invalid_argument(
280 "Serialized rank/select one samples disagree with source");
281 }
282 ++next_select1;
283 select1_milestone += kSelectSampleFrequency;
284 }
285 if (builds_select0(select_support_) &&
286 rank0 + zeros >= select0_milestone) {
287 const std::uint64_t zero_word =
288 ~word & first_bits_mask(static_cast<std::uint32_t>(word_bits));
289 const std::size_t position =
290 word_index * kWordSize +
291 select_64(zero_word, select0_milestone - rank0 - 1);
292 const std::uint64_t expected = position / kSuperBlockSize;
293 if (next_select0 == select0_end || samples[next_select0] != expected) {
294 throw std::invalid_argument(
295 "Serialized rank/select zero samples disagree with source");
296 }
297 ++next_select0;
298 select0_milestone += kSelectSampleFrequency;
299 }
300 basic_rank += ones;
301 rank1 += ones;
302 rank0 += zeros;
303 }
304
305 if (super_blocks.back() != max_rank_ ||
306 super_rank + basic_rank != max_rank_ || rank1 != max_rank_ ||
307 next_select1 != select1_end || next_select0 != select0_end) {
308 throw std::invalid_argument(
309 "Serialized rank/select totals disagree with source");
310 }
311 }
312
313 size_t logical_word_count() const {
314 return (num_bits_ + kWordSize - 1) / kWordSize;
315 }
316
317 size_t data_superblock_count() const {
318 return num_bits_ == 0 ? 0 : 1 + (num_bits_ - 1) / kSuperBlockSize;
319 }
320
321 template <StorageImplementation SourceStorage>
322 static ReadOnlyStorageView complete_word_view(
323 const SourceStorage& source_storage) {
324 if constexpr (requires { source_storage.padded_view(); }) {
325 return source_storage.padded_view();
326 } else {
327 return source_storage.view();
328 }
329 }
330
331 size_t stored_basicblock_count() const {
332 if (num_bits_ == 0) {
333 return 0;
334 }
335 const size_t data_basicblocks = 1 + (num_bits_ - 1) / kBasicBlockSize;
336 return (data_basicblocks + 31) / 32 * 32;
337 }
338
339 size_t logical_word_bits(size_t word_index) const {
340 const size_t begin = word_index * kWordSize;
341 if (begin >= num_bits_) {
342 return 0;
343 }
344 return std::min(kWordSize, num_bits_ - begin);
345 }
346
347 uint64_t logical_word(size_t word_index) const {
348 if (word_index >= bits_.size()) {
349 return 0;
350 }
351 const size_t bits = logical_word_bits(word_index);
352 if (bits == 0) {
353 return 0;
354 }
355 if (bits == kWordSize) {
356 return bits_[word_index];
357 }
358 return bits_[word_index] & first_bits_mask(bits);
359 }
360
361 uint64_t rank_in_basic_block(size_t basic_block, size_t offset) const {
362 if (offset == 0) {
363 return 0;
364 }
365 const size_t first_word = basic_block * kWordsPerBlock;
366 if (first_word + kWordsPerBlock <= bits_.size()) {
367 return rank_512(&bits_[first_word], offset);
368 }
369
370 uint64_t result = 0;
371 size_t word_index = first_word;
372 while (offset >= kWordSize) {
373 result += std::popcount(logical_word(word_index));
374 offset -= kWordSize;
375 ++word_index;
376 }
377 if (offset != 0) {
378 result +=
379 std::popcount(logical_word(word_index) & first_bits_mask(offset));
380 }
381 return result;
382 }
383
384 uint64_t select_in_words(size_t first_word, size_t rank, bool value) const {
385 const size_t first_bit = first_word * kWordSize;
386 if (first_bit + kBasicBlockSize <= num_bits_ &&
387 first_word + kWordsPerBlock <= bits_.size()) {
388 return value ? first_bit + select_512(&bits_[first_word], rank - 1)
389 : first_bit + select0_512(&bits_[first_word], rank - 1);
390 }
391
392 for (size_t word_index = first_word; word_index < logical_word_count();
393 ++word_index) {
394 const uint64_t word = logical_word(word_index);
395 const uint64_t candidates =
396 value ? word
397 : (~word & first_bits_mask(logical_word_bits(word_index)));
398 const size_t count = std::popcount(candidates);
399 if (rank > count) {
400 rank -= count;
401 continue;
402 }
403 return word_index * kWordSize + select_64(candidates, rank - 1);
404 }
405 return num_bits_;
406 }
407
408 static size_t select_sample_count_for_rank(size_t rank_count) {
409 return 1 + rank_count / kSelectSampleFrequency;
410 }
411
412 static size_t select_sample_upper_bound(size_t bit_count) {
413 return select_sample_count_for_rank(bit_count);
414 }
415
416 struct SelectSampleWriter {
417 std::span<uint64_t> words;
418 size_t next = 0;
419 size_t count = 0;
420 size_t capacity = 0;
421 bool enabled = false;
422 bool reversed = false;
423
424 SelectSampleWriter() = default;
425
426 SelectSampleWriter(std::span<uint64_t> words,
427 size_t begin,
428 size_t capacity,
429 bool enabled,
430 bool reversed)
431 : words(words),
432 next(begin),
433 capacity(capacity),
434 enabled(enabled),
435 reversed(reversed) {}
436
437 void append(uint64_t sample) {
438 if (!enabled) {
439 return;
440 }
441 if (count >= capacity) [[unlikely]] {
442 throw std::invalid_argument(
443 "RankSelectSupport one_count hint is inconsistent with input bits");
444 }
445 words[next] = sample;
446 ++count;
447 if (reversed) {
448 if (next != 0) {
449 --next;
450 }
451 } else {
452 ++next;
453 }
454 }
455 };
456
457 struct SelectSampleWriters {
458 SelectSampleWriter ones;
459 SelectSampleWriter zeros;
460 bool shrink_after_build = false;
461 };
462
463 SelectSampleWriters initialize_select_sample_writers(
464 bool need_select1,
465 bool need_select0,
466 std::optional<size_t> one_count) {
467 select1_sample_begin_ = 0;
468 select1_sample_count_ = 0;
469 select0_sample_begin_ = 0;
470 select0_sample_count_ = 0;
471 select0_samples_reversed_ = false;
472 select_samples_.resize(0);
473
474 SelectSampleWriters writers;
475 if (!need_select1 && !need_select0) {
476 return writers;
477 }
478
479 const std::optional<size_t> zero_count =
480 one_count ? std::optional<size_t>(num_bits_ - *one_count)
481 : std::nullopt;
482 if (need_select1 && need_select0) {
483 const size_t one_sample_capacity =
484 one_count ? select_sample_count_for_rank(*one_count)
485 : 2 + num_bits_ / kSelectSampleFrequency;
486 const size_t zero_sample_capacity =
487 zero_count ? select_sample_count_for_rank(*zero_count)
488 : 2 + num_bits_ / kSelectSampleFrequency;
489 const size_t total_samples =
490 one_count ? one_sample_capacity + zero_sample_capacity
491 : 2 + num_bits_ / kSelectSampleFrequency;
492 select_samples_.resize(total_samples * kWordSize);
493 auto samples = select_samples_.writable_words64();
494 select1_sample_begin_ = 0;
495 select0_samples_reversed_ = true;
496 writers.ones =
497 SelectSampleWriter(samples, 0, one_sample_capacity, true, false);
498 writers.zeros = SelectSampleWriter(samples, total_samples - 1,
499 zero_sample_capacity, true, true);
500 writers.ones.append(0);
501 writers.zeros.append(0);
502 return writers;
503 }
504
505 const size_t sample_capacity =
506 need_select1 ? (one_count ? select_sample_count_for_rank(*one_count)
507 : select_sample_upper_bound(num_bits_))
508 : (zero_count ? select_sample_count_for_rank(*zero_count)
509 : select_sample_upper_bound(num_bits_));
510 select_samples_.resize(sample_capacity * kWordSize);
511 auto samples = select_samples_.writable_words64();
512 writers.shrink_after_build = !one_count;
513 if (need_select1) {
514 select1_sample_begin_ = 0;
515 writers.ones =
516 SelectSampleWriter(samples, 0, sample_capacity, true, false);
517 writers.ones.append(0);
518 } else {
519 select0_sample_begin_ = 0;
520 writers.zeros =
521 SelectSampleWriter(samples, 0, sample_capacity, true, false);
522 writers.zeros.append(0);
523 }
524 return writers;
525 }
526
527 void finalize_select_sample_writers(SelectSampleWriters writers) {
528 select1_sample_count_ = writers.ones.count;
529 select0_sample_count_ = writers.zeros.count;
530 if (writers.zeros.reversed) {
531 select0_sample_begin_ = writers.zeros.next + 1;
532 auto zero_samples = writers.zeros.words.subspan(select0_sample_begin_,
533 select0_sample_count_);
534 std::reverse(zero_samples.begin(), zero_samples.end());
535 select0_samples_reversed_ = false;
536 }
537 if (writers.shrink_after_build) {
538 const size_t sample_count = select1_sample_count_ != 0
539 ? select1_sample_count_
540 : select0_sample_count_;
541 select_samples_.resize(sample_count * kWordSize);
542 select_samples_.shrink_to_fit();
543 }
544 }
545
546 uint64_t select1_sample(size_t sample_index) const {
547 auto samples = select_samples_.as_words64();
548 return samples[select1_sample_begin_ + sample_index];
549 }
550
551 uint64_t select0_sample(size_t sample_index) const {
552 auto samples = select_samples_.as_words64();
553 if (select0_samples_reversed_) {
554 return samples[select0_sample_begin_ + select0_sample_count_ - 1 -
555 sample_index];
556 }
557 return samples[select0_sample_begin_ + sample_index];
558 }
559
563 void build_rank_select(SelectSupport support,
564 std::optional<size_t> one_count) {
565 select_support_ = support;
566 const size_t data_superblocks = data_superblock_count();
567 // Keep complete 32-entry SIMD chunks. Full 128-entry superblock padding is
568 // unnecessary because only the terminal superblock can be partial. The
569 // extra super entry is the cumulative-rank sentinel used by interpolation
570 // search.
571 const size_t num_basicblocks = stored_basicblock_count();
572 super_block_rank_.resize((data_superblocks + 1) * 64);
573 basic_block_rank_.resize(num_basicblocks * 16);
574
575 auto super_block_rank = super_block_rank_.writable_words64();
576 auto basic_block_rank = basic_block_rank_.writable_words16();
577
578 const bool need_select1 = builds_select1(support);
579 const bool need_select0 = builds_select0(support);
580 if (one_count && *one_count > num_bits_) {
581 throw std::invalid_argument(
582 "RankSelectSupport one_count hint cannot exceed num_bits");
583 }
584 auto select_writers =
585 initialize_select_sample_writers(need_select1, need_select0, one_count);
586
587 uint64_t super_block_sum = 0;
588 uint64_t basic_block_sum = 0;
589 uint64_t milestone = kSelectSampleFrequency;
590 uint64_t milestone0 = kSelectSampleFrequency;
591 uint64_t rank = 0;
592 uint64_t rank0 = 0;
593
594 for (size_t i = 0; i / kBasicBlockSize < basic_block_rank.size();
595 i += kWordSize) {
596 if (i % kSuperBlockSize == 0) {
597 super_block_sum += basic_block_sum;
598 super_block_rank[i / kSuperBlockSize] = super_block_sum;
599 basic_block_sum = 0;
600 }
601 if (i % kBasicBlockSize == 0) {
602 basic_block_rank[i / kBasicBlockSize] =
603 static_cast<uint16_t>(basic_block_sum);
604 }
605 if (i / kWordSize < logical_word_count()) {
606 const size_t word_index = i / kWordSize;
607 const uint64_t word = logical_word(word_index);
608 const size_t word_bits = logical_word_bits(word_index);
609 const uint64_t ones = std::popcount(word);
610 const uint64_t zeros = word_bits - ones;
611 if (need_select1 && rank + ones >= milestone) {
612 const auto pos = select_64(word, milestone - rank - 1);
613 // TODO: try including global rank into select samples to save
614 // a cache miss on global rank scan
615 select_writers.ones.append((64 * word_index + pos) / kSuperBlockSize);
616 milestone += kSelectSampleFrequency;
617 }
618 if (need_select0 && rank0 + zeros >= milestone0) {
619 const uint64_t zero_word = ~word & first_bits_mask(word_bits);
620 const auto pos = select_64(zero_word, milestone0 - rank0 - 1);
621 select_writers.zeros.append((64 * word_index + pos) /
622 kSuperBlockSize);
623 milestone0 += kSelectSampleFrequency;
624 }
625 basic_block_sum += ones;
626 rank += ones;
627 rank0 += zeros;
628 }
629 }
630 max_rank_ = super_block_sum + basic_block_sum;
631 super_block_rank[data_superblocks] = max_rank_;
632 finalize_select_sample_writers(select_writers);
633 }
634
640 uint64_t find_superblock(uint64_t rank) const {
641 auto super_block_rank = super_block_rank_.as_words64();
642
643 uint64_t left = select1_sample(rank / kSelectSampleFrequency);
644
645 while (left + 7 < super_block_rank.size()) {
646 auto len = lower_bound_8x64(&super_block_rank[left], rank);
647 if (len < 8) {
648 return left + len - 1;
649 }
650 left += 8;
651 }
652 if (left + 3 < super_block_rank.size()) {
653 auto len = lower_bound_4x64(&super_block_rank[left], rank);
654 if (len < 4) {
655 return left + len - 1;
656 }
657 left += 4;
658 }
659 while (left < super_block_rank.size() && super_block_rank[left] < rank) {
660 left++;
661 }
662 return left - 1;
663 }
664
670 uint64_t find_superblock_zeros(uint64_t rank0) const {
671 auto super_block_rank = super_block_rank_.as_words64();
672
673 uint64_t left = select0_sample(rank0 / kSelectSampleFrequency);
674
675 while (left + 7 < super_block_rank.size()) {
676 auto len =
677 lower_bound_delta_8x64(&super_block_rank[left], rank0,
678 kDeltaSuper.data(), kSuperBlockSize * left);
679 if (len < 8) {
680 return left + len - 1;
681 }
682 left += 8;
683 }
684 if (left + 3 < super_block_rank.size()) {
685 auto len =
686 lower_bound_delta_4x64(&super_block_rank[left], rank0,
687 kDeltaSuper.data(), kSuperBlockSize * left);
688 if (len < 4) {
689 return left + len - 1;
690 }
691 left += 4;
692 }
693 while (left < super_block_rank.size() &&
694 kSuperBlockSize * left - super_block_rank[left] < rank0) {
695 left++;
696 }
697 return left - 1;
698 }
699
711 uint64_t find_basicblock(uint16_t local_rank, uint64_t s_block) const {
712 auto basic_block_rank = basic_block_rank_.as_words16();
713 const size_t block_begin = kBlocksPerSuperBlock * s_block;
714 const size_t block_count =
715 std::min(kBlocksPerSuperBlock, basic_block_rank.size() - block_begin);
716
717 for (size_t pos = 0; pos < block_count; pos += 32) {
718 auto count =
719 lower_bound_32x16(&basic_block_rank[block_begin + pos], local_rank);
720 if (count < 32) {
721 return block_begin + pos + count - 1;
722 }
723 }
724 return block_begin + block_count - 1;
725 }
726
738 uint64_t find_basicblock_zeros(uint16_t local_rank0, uint64_t s_block) const {
739 auto basic_block_rank = basic_block_rank_.as_words16();
740 const size_t block_begin = kBlocksPerSuperBlock * s_block;
741 const size_t block_count =
742 std::min(kBlocksPerSuperBlock, basic_block_rank.size() - block_begin);
743 for (size_t pos = 0; pos < block_count; pos += 32) {
744 auto count = lower_bound_delta_32x16(&basic_block_rank[block_begin + pos],
745 local_rank0, kDeltaBasic.data(),
746 kBasicBlockSize * pos);
747 if (count < 32) {
748 return block_begin + pos + count - 1;
749 }
750 }
751 return block_begin + block_count - 1;
752 }
753
771 uint64_t find_basicblock_is(uint16_t local_rank, uint64_t s_block) const {
772 auto super_block_rank = super_block_rank_.as_words64();
773 auto basic_block_rank = basic_block_rank_.as_words16();
774 const size_t block_begin = kBlocksPerSuperBlock * s_block;
775 const size_t block_count =
776 std::min(kBlocksPerSuperBlock, basic_block_rank.size() - block_begin);
777 const size_t last_group = block_count - 32;
778
779 auto lower = super_block_rank[s_block];
780 auto upper = super_block_rank[s_block + 1];
781
782 uint64_t pos = block_count * local_rank / (upper - lower);
783 pos = pos + 16 < 32 ? 0 : (pos - 16);
784 pos = std::min<uint64_t>(pos, last_group);
785 while (pos < last_group) {
786 auto count =
787 lower_bound_32x16(&basic_block_rank[block_begin + pos], local_rank);
788 if (count == 0) {
789 return find_basicblock(local_rank, s_block);
790 }
791 if (count < 32) {
792 return block_begin + pos + count - 1;
793 }
794 pos += 32;
795 }
796 pos = last_group;
797 auto count =
798 lower_bound_32x16(&basic_block_rank[block_begin + pos], local_rank);
799 if (count == 0) {
800 return find_basicblock(local_rank, s_block);
801 }
802 return block_begin + pos + count - 1;
803 }
804
814 uint64_t find_basicblock_is_zeros(uint16_t local_rank0,
815 uint64_t s_block) const {
816 auto super_block_rank = super_block_rank_.as_words64();
817 auto basic_block_rank = basic_block_rank_.as_words16();
818 const size_t block_begin = kBlocksPerSuperBlock * s_block;
819 const size_t block_count =
820 std::min(kBlocksPerSuperBlock, basic_block_rank.size() - block_begin);
821 const size_t last_group = block_count - 32;
822
823 auto lower = kSuperBlockSize * s_block - super_block_rank[s_block];
824 auto upper =
825 kSuperBlockSize * (s_block + 1) - super_block_rank[s_block + 1];
826
827 uint64_t interpolation = block_count * local_rank0 / (upper - lower);
828 // Random data usually places the interpolation estimate in the target
829 // block. Validate it from existing one-prefix metadata before the SIMD
830 // derived-zero scan.
831 const uint64_t block_offset =
832 std::min<uint64_t>(interpolation, block_count - 1);
833 const uint64_t block = block_begin + block_offset;
834 const uint64_t zero_before =
835 kBasicBlockSize * block_offset - basic_block_rank[block];
836 const uint64_t zero_after = block_offset + 1 == block_count
837 ? upper - lower
838 : kBasicBlockSize * (block_offset + 1) -
839 basic_block_rank[block + 1];
840 if (zero_before < local_rank0 && local_rank0 <= zero_after) {
841 return block;
842 }
843
844 uint64_t pos = interpolation;
845 pos = pos + 16 < 32 ? 0 : (pos - 16);
846 pos = std::min<uint64_t>(pos, last_group);
847 while (pos < last_group) {
848 auto count = lower_bound_delta_32x16(&basic_block_rank[block_begin + pos],
849 local_rank0, kDeltaBasic.data(),
850 kBasicBlockSize * pos);
851 if (count == 0) {
852 return find_basicblock_zeros(local_rank0, s_block);
853 }
854 if (count < 32) {
855 return block_begin + pos + count - 1;
856 }
857 pos += 32;
858 }
859 pos = last_group;
860 auto count = lower_bound_delta_32x16(&basic_block_rank[block_begin + pos],
861 local_rank0, kDeltaBasic.data(),
862 kBasicBlockSize * pos);
863 if (count == 0) {
864 return find_basicblock_zeros(local_rank0, s_block);
865 }
866 return block_begin + pos + count - 1;
867 }
868
869 public:
870 RankSelectSupport() = default;
871 RankSelectSupport(const RankSelectSupport&) = default;
872 RankSelectSupport(RankSelectSupport&&) noexcept = default;
873 RankSelectSupport& operator=(const RankSelectSupport&) = default;
874 RankSelectSupport& operator=(RankSelectSupport&&) noexcept = default;
875
876#ifdef PIXIE_DIAGNOSTICS
877 struct DiagnosticsBytes {
878 size_t source_bit_sequence_bytes = 0;
879 size_t super_block_rank_bytes = 0;
880 size_t basic_block_rank_bytes = 0;
881 size_t select1_samples_bytes = 0;
882 size_t select0_samples_bytes = 0;
883 size_t total_bytes = 0;
884 };
885
889 DiagnosticsBytes diagnostics_bytes() const {
890 DiagnosticsBytes result;
891 result.source_bit_sequence_bytes = (num_bits_ + 7) / 8;
892 result.super_block_rank_bytes = super_block_rank_.as_bytes().size();
893 result.basic_block_rank_bytes = basic_block_rank_.as_bytes().size();
894 result.select1_samples_bytes = select1_sample_count_ * sizeof(uint64_t);
895 result.select0_samples_bytes = select0_sample_count_ * sizeof(uint64_t);
896 result.total_bytes = result.super_block_rank_bytes +
897 result.basic_block_rank_bytes +
898 select_samples_.as_bytes().size();
899 return result;
900 }
901
905 void memory_report() const {
906 const auto diagnostics = diagnostics_bytes();
907 const double source_bytes =
908 static_cast<double>(diagnostics.source_bit_sequence_bytes);
909 const auto log_bytes = [&](std::string_view label, size_t bytes) {
910 const double percentage =
911 source_bytes > 0.0 ? 100.0 * static_cast<double>(bytes) / source_bytes
912 : 0.0;
913 spdlog::info("RankSelectSupport {}: {} bytes ({:.2f}% of source)", label,
914 bytes, percentage);
915 };
916 log_bytes("source_bit_sequence", diagnostics.source_bit_sequence_bytes);
917 log_bytes("super_block_rank", diagnostics.super_block_rank_bytes);
918 log_bytes("basic_block_rank", diagnostics.basic_block_rank_bytes);
919 log_bytes("select1_samples", diagnostics.select1_samples_bytes);
920 log_bytes("select0_samples", diagnostics.select0_samples_bytes);
921 log_bytes("total", diagnostics.total_bytes);
922 }
923#endif
934 ReadOnlyStorageView source_storage,
935 size_t num_bits,
936 SelectSupport select_support = SelectSupport::kBoth,
937 std::optional<size_t> one_count = std::nullopt)
938 : source_storage_(source_storage),
939 bits_(source_storage_.as_words64()),
940 num_bits_(std::min(num_bits, bits_.size() * kWordSize)),
941 padded_size_(((num_bits_ + kWordSize - 1) / kWordSize) * kWordSize) {
942 build_rank_select(select_support, one_count);
943 }
944
953 std::span<const uint64_t> source_words,
954 size_t num_bits,
955 SelectSupport select_support = SelectSupport::kBoth,
956 std::optional<size_t> one_count = std::nullopt)
957 : RankSelectSupport(ReadOnlyStorageView(std::as_bytes(source_words)),
958 num_bits,
959 select_support,
960 one_count) {}
961
971 template <StorageImplementation SourceStorage>
973 const SourceStorage& source_storage,
974 size_t num_bits,
975 SelectSupport select_support = SelectSupport::kBoth,
976 std::optional<size_t> one_count = std::nullopt)
977 : RankSelectSupport(complete_word_view(source_storage),
978 std::min(num_bits, source_storage.size_bits()),
979 select_support,
980 one_count) {}
981
985 size_t size_impl() const { return num_bits_; }
986
990 bool supports_select1_impl() const { return builds_select1(select_support_); }
991
995 bool supports_select0_impl() const { return builds_select0(select_support_); }
996
1004 requires requires(const MetadataStorage& storage) {
1005 storage.allocated_bytes();
1006 }
1007 {
1008 return sizeof(*this) + super_block_rank_.allocated_bytes() +
1009 basic_block_rank_.allocated_bytes() +
1010 select_samples_.allocated_bytes();
1011 }
1012
1018 int bit_impl(size_t pos) const {
1019 size_t word_idx = pos / kWordSize;
1020 size_t bit_off = pos % kWordSize;
1021
1022 return (bits_[word_idx] >> bit_off) & 1;
1023 }
1024
1031 uint64_t rank_impl(size_t pos) const {
1032 if (pos >= num_bits_) [[unlikely]] {
1033 return max_rank_;
1034 }
1035
1036 auto super_block_rank = super_block_rank_.as_words64();
1037 auto basic_block_rank = basic_block_rank_.as_words16();
1038
1039 uint64_t b_block = pos / kBasicBlockSize;
1040 uint64_t s_block = pos / kSuperBlockSize;
1041 // Precomputed rank
1042 uint64_t result = super_block_rank[s_block] + basic_block_rank[b_block];
1043 // Basic block tail
1044 result += rank_in_basic_block(b_block, pos - (b_block * kBasicBlockSize));
1045 return result;
1046 }
1047
1054 uint64_t select_impl(size_t rank) const {
1055 if (rank == 0) [[unlikely]] {
1056 return 0;
1057 }
1058 if (!supports_select1_impl()) [[unlikely]] {
1059 return num_bits_;
1060 }
1061 if (rank > max_rank_) [[unlikely]] {
1062 return num_bits_;
1063 }
1064 auto super_block_rank = super_block_rank_.as_words64();
1065 auto basic_block_rank = basic_block_rank_.as_words16();
1066
1067 uint64_t s_block = find_superblock(rank);
1068 rank -= super_block_rank[s_block];
1069 auto pos = find_basicblock_is(rank, s_block);
1070 rank -= basic_block_rank[pos];
1071 return select_in_words(pos * kWordsPerBlock, rank, true);
1072 }
1073
1081 uint64_t select0_impl(size_t rank0) const {
1082 if (rank0 == 0) [[unlikely]] {
1083 return 0;
1084 }
1085 if (!supports_select0_impl()) [[unlikely]] {
1086 return num_bits_;
1087 }
1088 if (rank0 > num_bits_ - max_rank_) [[unlikely]] {
1089 return num_bits_;
1090 }
1091 auto super_block_rank = super_block_rank_.as_words64();
1092 auto basic_block_rank = basic_block_rank_.as_words16();
1093
1094 uint64_t s_block = find_superblock_zeros(rank0);
1095 rank0 -= kSuperBlockSize * s_block - super_block_rank[s_block];
1096 auto pos = find_basicblock_is_zeros(rank0, s_block);
1097 auto pos_in_super_block = pos & (kBlocksPerSuperBlock - 1);
1098 rank0 -= kBasicBlockSize * pos_in_super_block - basic_block_rank[pos];
1099 return select_in_words(pos * kWordsPerBlock, rank0, false);
1100 }
1101
1108 void serialize_impl(BinaryWriter& writer) const {
1109 writer.write_size(num_bits_);
1110 writer.write_size(padded_size_);
1111 writer.write_size(max_rank_);
1112 writer.write_size(select1_sample_begin_);
1113 writer.write_size(select1_sample_count_);
1114 writer.write_size(select0_sample_begin_);
1115 writer.write_size(select0_sample_count_);
1116 writer.write_u32(static_cast<std::uint32_t>(select_support_));
1117 writer.write_u32(static_cast<std::uint32_t>(select0_samples_reversed_));
1118 super_block_rank_.serialize(writer);
1119 basic_block_rank_.serialize(writer);
1120 select_samples_.serialize(writer);
1121 }
1122
1140 static RankSelectSupport deserialize_impl(
1141 BinaryReader& reader,
1142 std::span<const uint64_t> source_bits,
1144 requires(std::same_as<MetadataStorage, AlignedStorage> ||
1145 std::same_as<MetadataStorage, ReadOnlyStorageView>)
1146 {
1147 BinaryReader candidate = reader;
1148 RankSelectSupport result;
1149 result.source_storage_ = ReadOnlyStorageView(std::as_bytes(source_bits));
1150 result.bits_ = result.source_storage_.as_words64();
1151 result.num_bits_ = candidate.read_size();
1152 result.padded_size_ = candidate.read_size();
1153 result.max_rank_ = candidate.read_size();
1154 result.select1_sample_begin_ = candidate.read_size();
1155 result.select1_sample_count_ = candidate.read_size();
1156 result.select0_sample_begin_ = candidate.read_size();
1157 result.select0_sample_count_ = candidate.read_size();
1158 const std::uint32_t support = candidate.read_u32();
1159 if (support > static_cast<std::uint32_t>(SelectSupport::kBoth)) {
1160 throw std::invalid_argument(
1161 "Invalid serialized rank/select configuration");
1162 }
1163 result.select_support_ = static_cast<SelectSupport>(support);
1164 const std::uint32_t reversed = candidate.read_u32();
1165 if (reversed > 1) {
1166 throw std::invalid_argument(
1167 "Invalid serialized rank/select boolean value");
1168 }
1169 result.select0_samples_reversed_ = reversed != 0;
1170 result.super_block_rank_ = deserialize_metadata_storage(candidate);
1171 result.basic_block_rank_ = deserialize_metadata_storage(candidate);
1172 result.select_samples_ = deserialize_metadata_storage(candidate);
1173 result.validate_deserialized_state(validation);
1174 reader = candidate;
1175 return result;
1176 }
1177};
1178
1180using RankSelectSupportView = RankSelectSupport<ReadOnlyStorageView>;
1181
1182} // namespace pixie
Bounds-checked reader for canonical little-endian binary data.
Definition serialization.h:526
std::uint32_t read_u32()
Read an unsigned little-endian 32-bit integer.
Definition serialization.h:558
std::size_t read_size()
Read an unsigned 64-bit size and convert it to size_t.
Definition serialization.h:587
Bounded-buffer writer for canonical little-endian binary data.
Definition serialization.h:198
void write_u32(std::uint32_t value)
Write an unsigned 32-bit integer in little-endian order.
Definition serialization.h:258
void write_size(std::size_t value)
Write a platform size as an unsigned 64-bit integer.
Definition serialization.h:287
CRTP facade for immutable rank/select queries and bit access.
Definition rank_select.h:25
std::uint64_t rank0(std::size_t end_position) const
Definition rank_select.h:62
std::size_t size() const
Definition rank_select.h:31
std::uint64_t rank(std::size_t end_position) const
Definition rank_select.h:53
uint64_t rank_impl(size_t pos) const
Rank of 1s up to position pos (exclusive).
Definition support.h:1031
size_t memory_usage_bytes_impl() const
Return owned auxiliary memory usage in bytes.
Definition support.h:1003
RankSelectSupport(const SourceStorage &source_storage, size_t num_bits, SelectSupport select_support=SelectSupport::kBoth, std::optional< size_t > one_count=std::nullopt)
Construct support over a Pixie storage implementation.
Definition support.h:972
bool supports_select1_impl() const
Whether this index stores samples for select1 queries.
Definition support.h:990
bool supports_select0_impl() const
Whether this index stores samples for select0 queries.
Definition support.h:995
static RankSelectSupport deserialize_impl(BinaryReader &reader, std::span< const uint64_t > source_bits, DeserializationValidation validation=DeserializationValidation::kQuick)
Restore serialized metadata over caller-owned source bits.
Definition support.h:1140
RankSelectSupport(std::span< const uint64_t > source_words, size_t num_bits, SelectSupport select_support=SelectSupport::kBoth, std::optional< size_t > one_count=std::nullopt)
Construct support over caller-owned packed 64-bit words.
Definition support.h:952
uint64_t select0_impl(size_t rank0) const
Select the position of the rank0-th 0-bit (1-indexed).
Definition support.h:1081
SelectSupport
Select directions to index during construction.
Definition support.h:63
int bit_impl(size_t pos) const
Returns the bit at the given position.
Definition support.h:1018
size_t size_impl() const
Returns the number of valid bits.
Definition support.h:985
RankSelectSupport(ReadOnlyStorageView source_storage, size_t num_bits, SelectSupport select_support=SelectSupport::kBoth, std::optional< size_t > one_count=std::nullopt)
Construct support over a read-only storage view.
Definition support.h:933
void serialize_impl(BinaryWriter &writer) const
Serialize rank/select metadata in canonical little-endian form.
Definition support.h:1108
uint64_t select_impl(size_t rank) const
Rank of 0s up to position pos (exclusive).
Definition support.h:1054
A non-owning, read-only view of a byte sequence.
Definition read_only_view.h:15
CRTP facade for optional binary serialization and deserialization.
Definition serialization.h:693
std::span< const std::uint64_t > as_words64() const
Return a read-only view as 64-bit words.
Definition storage.h:181
Common interface for rank/select support over packed bit sequences.
DeserializationValidation
Validation strength used while restoring serialized indexes.
Definition serialization.h:28
@ kQuick
Check framing, dimensions, references, and other conditions needed for memory-safe terminating querie...
Definition serialization.h:33