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 for (size_t pos = 0; pos < block_count; pos += 32) {
717 auto count =
718 lower_bound_32x16(&basic_block_rank[block_begin + pos], local_rank);
719 if (count < 32) {
720 return block_begin + pos + count - 1;
721 }
722 }
723 return block_begin + block_count - 1;
724 }
725
737 uint64_t find_basicblock_zeros(uint16_t local_rank0, uint64_t s_block) const {
738 auto basic_block_rank = basic_block_rank_.as_words16();
739 const size_t block_begin = kBlocksPerSuperBlock * s_block;
740 const size_t block_count =
741 std::min(kBlocksPerSuperBlock, basic_block_rank.size() - block_begin);
742 for (size_t pos = 0; pos < block_count; pos += 32) {
743 auto count = lower_bound_delta_32x16(&basic_block_rank[block_begin + pos],
744 local_rank0, kDeltaBasic.data(),
745 kBasicBlockSize * pos);
746 if (count < 32) {
747 return block_begin + pos + count - 1;
748 }
749 }
750 return block_begin + block_count - 1;
751 }
752
765 uint64_t find_basicblock_is(uint16_t local_rank, uint64_t s_block) const {
766 auto super_block_rank = super_block_rank_.as_words64();
767 auto basic_block_rank = basic_block_rank_.as_words16();
768 const size_t block_begin = kBlocksPerSuperBlock * s_block;
769 const size_t block_count =
770 std::min(kBlocksPerSuperBlock, basic_block_rank.size() - block_begin);
771 const size_t last_group = block_count - 32;
772
773 auto lower = super_block_rank[s_block];
774 auto upper = super_block_rank[s_block + 1];
775
776 uint64_t interpolation = block_count * local_rank / (upper - lower);
777 const uint64_t block_offset =
778 std::min<uint64_t>(interpolation, block_count - 1);
779 const uint64_t block = block_begin + block_offset;
780 const uint64_t ones_before = basic_block_rank[block];
781 const uint64_t ones_after = block_offset + 1 == block_count
782 ? upper - lower
783 : basic_block_rank[block + 1];
784 if (ones_before < local_rank && local_rank <= ones_after) {
785 return block;
786 }
787
788 uint64_t pos = interpolation;
789 pos = pos + 16 < 32 ? 0 : (pos - 16);
790 pos = std::min<uint64_t>(pos, last_group);
791 while (pos < last_group) {
792 auto count =
793 lower_bound_32x16(&basic_block_rank[block_begin + pos], local_rank);
794 if (count == 0) {
795 return find_basicblock(local_rank, s_block);
796 }
797 if (count < 32) {
798 return block_begin + pos + count - 1;
799 }
800 pos += 32;
801 }
802 pos = last_group;
803 auto count =
804 lower_bound_32x16(&basic_block_rank[block_begin + pos], local_rank);
805 if (count == 0) {
806 return find_basicblock(local_rank, s_block);
807 }
808 return block_begin + pos + count - 1;
809 }
810
820 uint64_t find_basicblock_is_zeros(uint16_t local_rank0,
821 uint64_t s_block) const {
822 auto super_block_rank = super_block_rank_.as_words64();
823 auto basic_block_rank = basic_block_rank_.as_words16();
824 const size_t block_begin = kBlocksPerSuperBlock * s_block;
825 const size_t block_count =
826 std::min(kBlocksPerSuperBlock, basic_block_rank.size() - block_begin);
827 const size_t last_group = block_count - 32;
828
829 auto lower = kSuperBlockSize * s_block - super_block_rank[s_block];
830 auto upper =
831 kSuperBlockSize * (s_block + 1) - super_block_rank[s_block + 1];
832
833 uint64_t interpolation = block_count * local_rank0 / (upper - lower);
834 // Random data usually places the interpolation estimate in the target
835 // block. Validate it from existing one-prefix metadata before the SIMD
836 // derived-zero scan.
837 const uint64_t block_offset =
838 std::min<uint64_t>(interpolation, block_count - 1);
839 const uint64_t block = block_begin + block_offset;
840 const uint64_t zero_before =
841 kBasicBlockSize * block_offset - basic_block_rank[block];
842 const uint64_t zero_after = block_offset + 1 == block_count
843 ? upper - lower
844 : kBasicBlockSize * (block_offset + 1) -
845 basic_block_rank[block + 1];
846 if (zero_before < local_rank0 && local_rank0 <= zero_after) {
847 return block;
848 }
849
850 uint64_t pos = interpolation;
851 pos = pos + 16 < 32 ? 0 : (pos - 16);
852 pos = std::min<uint64_t>(pos, last_group);
853 while (pos < last_group) {
854 auto count = lower_bound_delta_32x16(&basic_block_rank[block_begin + pos],
855 local_rank0, kDeltaBasic.data(),
856 kBasicBlockSize * pos);
857 if (count == 0) {
858 return find_basicblock_zeros(local_rank0, s_block);
859 }
860 if (count < 32) {
861 return block_begin + pos + count - 1;
862 }
863 pos += 32;
864 }
865 pos = last_group;
866 auto count = lower_bound_delta_32x16(&basic_block_rank[block_begin + pos],
867 local_rank0, kDeltaBasic.data(),
868 kBasicBlockSize * pos);
869 if (count == 0) {
870 return find_basicblock_zeros(local_rank0, s_block);
871 }
872 return block_begin + pos + count - 1;
873 }
874
875 public:
876 RankSelectSupport() = default;
877 RankSelectSupport(const RankSelectSupport&) = default;
878 RankSelectSupport(RankSelectSupport&&) noexcept = default;
879 RankSelectSupport& operator=(const RankSelectSupport&) = default;
880 RankSelectSupport& operator=(RankSelectSupport&&) noexcept = default;
881
882#ifdef PIXIE_DIAGNOSTICS
883 struct DiagnosticsBytes {
884 size_t source_bit_sequence_bytes = 0;
885 size_t super_block_rank_bytes = 0;
886 size_t basic_block_rank_bytes = 0;
887 size_t select1_samples_bytes = 0;
888 size_t select0_samples_bytes = 0;
889 size_t total_bytes = 0;
890 };
891
895 DiagnosticsBytes diagnostics_bytes() const {
896 DiagnosticsBytes result;
897 result.source_bit_sequence_bytes = (num_bits_ + 7) / 8;
898 result.super_block_rank_bytes = super_block_rank_.as_bytes().size();
899 result.basic_block_rank_bytes = basic_block_rank_.as_bytes().size();
900 result.select1_samples_bytes = select1_sample_count_ * sizeof(uint64_t);
901 result.select0_samples_bytes = select0_sample_count_ * sizeof(uint64_t);
902 result.total_bytes = result.super_block_rank_bytes +
903 result.basic_block_rank_bytes +
904 select_samples_.as_bytes().size();
905 return result;
906 }
907
911 void memory_report() const {
912 const auto diagnostics = diagnostics_bytes();
913 const double source_bytes =
914 static_cast<double>(diagnostics.source_bit_sequence_bytes);
915 const auto log_bytes = [&](std::string_view label, size_t bytes) {
916 const double percentage =
917 source_bytes > 0.0 ? 100.0 * static_cast<double>(bytes) / source_bytes
918 : 0.0;
919 spdlog::info("RankSelectSupport {}: {} bytes ({:.2f}% of source)", label,
920 bytes, percentage);
921 };
922 log_bytes("source_bit_sequence", diagnostics.source_bit_sequence_bytes);
923 log_bytes("super_block_rank", diagnostics.super_block_rank_bytes);
924 log_bytes("basic_block_rank", diagnostics.basic_block_rank_bytes);
925 log_bytes("select1_samples", diagnostics.select1_samples_bytes);
926 log_bytes("select0_samples", diagnostics.select0_samples_bytes);
927 log_bytes("total", diagnostics.total_bytes);
928 }
929#endif
940 ReadOnlyStorageView source_storage,
941 size_t num_bits,
942 SelectSupport select_support = SelectSupport::kBoth,
943 std::optional<size_t> one_count = std::nullopt)
944 : source_storage_(source_storage),
945 bits_(source_storage_.as_words64()),
946 num_bits_(std::min(num_bits, bits_.size() * kWordSize)),
947 padded_size_(((num_bits_ + kWordSize - 1) / kWordSize) * kWordSize) {
948 build_rank_select(select_support, one_count);
949 }
950
959 std::span<const uint64_t> source_words,
960 size_t num_bits,
961 SelectSupport select_support = SelectSupport::kBoth,
962 std::optional<size_t> one_count = std::nullopt)
963 : RankSelectSupport(ReadOnlyStorageView(std::as_bytes(source_words)),
964 num_bits,
965 select_support,
966 one_count) {}
967
977 template <StorageImplementation SourceStorage>
979 const SourceStorage& source_storage,
980 size_t num_bits,
981 SelectSupport select_support = SelectSupport::kBoth,
982 std::optional<size_t> one_count = std::nullopt)
983 : RankSelectSupport(complete_word_view(source_storage),
984 std::min(num_bits, source_storage.size_bits()),
985 select_support,
986 one_count) {}
987
991 size_t size_impl() const { return num_bits_; }
992
996 bool supports_select1_impl() const { return builds_select1(select_support_); }
997
1001 bool supports_select0_impl() const { return builds_select0(select_support_); }
1002
1010 requires requires(const MetadataStorage& storage) {
1011 storage.allocated_bytes();
1012 }
1013 {
1014 return sizeof(*this) + super_block_rank_.allocated_bytes() +
1015 basic_block_rank_.allocated_bytes() +
1016 select_samples_.allocated_bytes();
1017 }
1018
1024 int bit_impl(size_t pos) const {
1025 size_t word_idx = pos / kWordSize;
1026 size_t bit_off = pos % kWordSize;
1027
1028 return (bits_[word_idx] >> bit_off) & 1;
1029 }
1030
1037 uint64_t rank_impl(size_t pos) const {
1038 if (pos >= num_bits_) [[unlikely]] {
1039 return max_rank_;
1040 }
1041
1042 auto super_block_rank = super_block_rank_.as_words64();
1043 auto basic_block_rank = basic_block_rank_.as_words16();
1044
1045 uint64_t b_block = pos / kBasicBlockSize;
1046 uint64_t s_block = pos / kSuperBlockSize;
1047 // Precomputed rank
1048 uint64_t result = super_block_rank[s_block] + basic_block_rank[b_block];
1049 // Basic block tail
1050 result += rank_in_basic_block(b_block, pos - (b_block * kBasicBlockSize));
1051 return result;
1052 }
1053
1060 uint64_t select_impl(size_t rank) const {
1061 if (rank == 0) [[unlikely]] {
1062 return 0;
1063 }
1064 if (!supports_select1_impl()) [[unlikely]] {
1065 return num_bits_;
1066 }
1067 if (rank > max_rank_) [[unlikely]] {
1068 return num_bits_;
1069 }
1070 auto super_block_rank = super_block_rank_.as_words64();
1071 auto basic_block_rank = basic_block_rank_.as_words16();
1072
1073 uint64_t s_block = find_superblock(rank);
1074 rank -= super_block_rank[s_block];
1075 const uint64_t pos =
1076 rank == kSuperBlockSize
1077 ? s_block * kBlocksPerSuperBlock + (kBlocksPerSuperBlock - 1)
1078 : find_basicblock_is(static_cast<uint16_t>(rank), s_block);
1079 rank -= basic_block_rank[pos];
1080 return select_in_words(pos * kWordsPerBlock, rank, true);
1081 }
1082
1090 uint64_t select0_impl(size_t rank0) const {
1091 if (rank0 == 0) [[unlikely]] {
1092 return 0;
1093 }
1094 if (!supports_select0_impl()) [[unlikely]] {
1095 return num_bits_;
1096 }
1097 if (rank0 > num_bits_ - max_rank_) [[unlikely]] {
1098 return num_bits_;
1099 }
1100 auto super_block_rank = super_block_rank_.as_words64();
1101 auto basic_block_rank = basic_block_rank_.as_words16();
1102
1103 uint64_t s_block = find_superblock_zeros(rank0);
1104 rank0 -= kSuperBlockSize * s_block - super_block_rank[s_block];
1105 const uint64_t pos =
1106 rank0 == kSuperBlockSize
1107 ? s_block * kBlocksPerSuperBlock + (kBlocksPerSuperBlock - 1)
1108 : find_basicblock_is_zeros(static_cast<uint16_t>(rank0), s_block);
1109 auto pos_in_super_block = pos & (kBlocksPerSuperBlock - 1);
1110 rank0 -= kBasicBlockSize * pos_in_super_block - basic_block_rank[pos];
1111 return select_in_words(pos * kWordsPerBlock, rank0, false);
1112 }
1113
1120 void serialize_impl(BinaryWriter& writer) const {
1121 writer.write_size(num_bits_);
1122 writer.write_size(padded_size_);
1123 writer.write_size(max_rank_);
1124 writer.write_size(select1_sample_begin_);
1125 writer.write_size(select1_sample_count_);
1126 writer.write_size(select0_sample_begin_);
1127 writer.write_size(select0_sample_count_);
1128 writer.write_u32(static_cast<std::uint32_t>(select_support_));
1129 writer.write_u32(static_cast<std::uint32_t>(select0_samples_reversed_));
1130 super_block_rank_.serialize(writer);
1131 basic_block_rank_.serialize(writer);
1132 select_samples_.serialize(writer);
1133 }
1134
1152 static RankSelectSupport deserialize_impl(
1153 BinaryReader& reader,
1154 std::span<const uint64_t> source_bits,
1156 requires(std::same_as<MetadataStorage, AlignedStorage> ||
1157 std::same_as<MetadataStorage, ReadOnlyStorageView>)
1158 {
1159 BinaryReader candidate = reader;
1160 RankSelectSupport result;
1161 result.source_storage_ = ReadOnlyStorageView(std::as_bytes(source_bits));
1162 result.bits_ = result.source_storage_.as_words64();
1163 result.num_bits_ = candidate.read_size();
1164 result.padded_size_ = candidate.read_size();
1165 result.max_rank_ = candidate.read_size();
1166 result.select1_sample_begin_ = candidate.read_size();
1167 result.select1_sample_count_ = candidate.read_size();
1168 result.select0_sample_begin_ = candidate.read_size();
1169 result.select0_sample_count_ = candidate.read_size();
1170 const std::uint32_t support = candidate.read_u32();
1171 if (support > static_cast<std::uint32_t>(SelectSupport::kBoth)) {
1172 throw std::invalid_argument(
1173 "Invalid serialized rank/select configuration");
1174 }
1175 result.select_support_ = static_cast<SelectSupport>(support);
1176 const std::uint32_t reversed = candidate.read_u32();
1177 if (reversed > 1) {
1178 throw std::invalid_argument(
1179 "Invalid serialized rank/select boolean value");
1180 }
1181 result.select0_samples_reversed_ = reversed != 0;
1182 result.super_block_rank_ = deserialize_metadata_storage(candidate);
1183 result.basic_block_rank_ = deserialize_metadata_storage(candidate);
1184 result.select_samples_ = deserialize_metadata_storage(candidate);
1185 result.validate_deserialized_state(validation);
1186 reader = candidate;
1187 return result;
1188 }
1189};
1190
1192using RankSelectSupportView = RankSelectSupport<ReadOnlyStorageView>;
1193
1194} // 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:32
std::uint64_t rank0(std::size_t end_position) const
Definition rank_select.h:78
std::size_t size() const
Definition rank_select.h:39
std::uint64_t rank(std::size_t end_position) const
Definition rank_select.h:66
uint64_t rank_impl(size_t pos) const
Rank of 1s up to position pos (exclusive).
Definition support.h:1037
size_t memory_usage_bytes_impl() const
Return owned auxiliary memory usage in bytes.
Definition support.h:1009
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:978
bool supports_select1_impl() const
Whether this index stores samples for select1 queries.
Definition support.h:996
bool supports_select0_impl() const
Whether this index stores samples for select0 queries.
Definition support.h:1001
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:1152
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:958
uint64_t select0_impl(size_t rank0) const
Select the position of the rank0-th 0-bit (1-indexed).
Definition support.h:1090
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:1024
size_t size_impl() const
Returns the number of valid bits.
Definition support.h:991
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:939
void serialize_impl(BinaryWriter &writer) const
Serialize rank/select metadata in canonical little-endian form.
Definition support.h:1120
uint64_t select_impl(size_t rank) const
Rank of 0s up to position pos (exclusive).
Definition support.h:1060
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