Pixie
Loading...
Searching...
No Matches
support.h
1#pragma once
2
3#include <pixie/bit_stream.h>
4#include <pixie/bits.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 <bit>
11#include <concepts>
12#include <cstdint>
13#include <optional>
14#include <span>
15#include <stdexcept>
16#include <string>
17
18#ifdef PIXIE_DIAGNOSTICS
19#include <spdlog/spdlog.h>
20#endif
21
22namespace pixie {
23
53template <StorageImplementation MetadataStorage = AlignedStorage>
54class RankSelectSupport
55 : public RankSelectBase<RankSelectSupport<MetadataStorage>> {
56 public:
60 enum class SelectSupport : uint8_t {
61 kNone = 0,
62 kSelect1 = 1,
63 kSelect0 = 2,
64 kBoth = 3,
65 };
66
67 private:
68 constexpr static size_t kWordSize = 64;
69 constexpr static size_t kSuperBlockRankIntSize = 64;
70 constexpr static size_t kBasicBlockRankIntSize = 16;
71 constexpr static size_t kBasicBlockSize = 512;
72 constexpr static size_t kWordsPerBlock = 8;
73 constexpr static size_t kSuperBlockSize = 65536;
74 constexpr static size_t kBlocksPerSuperBlock = 128;
75 constexpr static size_t kSelectSampleFrequency = 16384;
76
77 alignas(64) uint64_t delta_super[8]{};
78 alignas(64) uint16_t delta_basic[32]{};
79
80 MetadataStorage super_block_rank_; // 64-bit global prefix sums
81 MetadataStorage basic_block_rank_; // 16-bit local prefix sums
82 MetadataStorage select_samples_; // 64-bit global positions
83 ReadOnlyStorageView source_storage_;
84 std::span<const uint64_t> bits_;
85 size_t num_bits_{};
86 size_t padded_size_{};
87 size_t max_rank_{};
88 size_t select1_sample_begin_{};
89 size_t select1_sample_count_{};
90 size_t select0_sample_begin_{};
91 size_t select0_sample_count_{};
92 SelectSupport select_support_ = SelectSupport::kNone;
93 bool select0_samples_reversed_ = false;
94
95 static bool builds_select1(SelectSupport support) {
96 return (static_cast<uint8_t>(support) &
97 static_cast<uint8_t>(SelectSupport::kSelect1)) != 0;
98 }
99
100 static bool builds_select0(SelectSupport support) {
101 return (static_cast<uint8_t>(support) &
102 static_cast<uint8_t>(SelectSupport::kSelect0)) != 0;
103 }
104
105 size_t logical_word_count() const {
106 return (num_bits_ + kWordSize - 1) / kWordSize;
107 }
108
109 size_t logical_word_bits(size_t word_index) const {
110 const size_t begin = word_index * kWordSize;
111 if (begin >= num_bits_) {
112 return 0;
113 }
114 return std::min(kWordSize, num_bits_ - begin);
115 }
116
117 uint64_t logical_word(size_t word_index) const {
118 if (word_index >= bits_.size()) {
119 return 0;
120 }
121 const size_t bits = logical_word_bits(word_index);
122 if (bits == 0) {
123 return 0;
124 }
125 if (bits == kWordSize) {
126 return bits_[word_index];
127 }
128 return bits_[word_index] & first_bits_mask(bits);
129 }
130
131 uint64_t rank_in_basic_block(size_t basic_block, size_t offset) const {
132 if (offset == 0) {
133 return 0;
134 }
135 const size_t first_word = basic_block * kWordsPerBlock;
136 if (first_word + kWordsPerBlock <= bits_.size()) {
137 return rank_512(&bits_[first_word], offset);
138 }
139
140 uint64_t result = 0;
141 size_t word_index = first_word;
142 while (offset >= kWordSize) {
143 result += std::popcount(logical_word(word_index));
144 offset -= kWordSize;
145 ++word_index;
146 }
147 if (offset != 0) {
148 result +=
149 std::popcount(logical_word(word_index) & first_bits_mask(offset));
150 }
151 return result;
152 }
153
154 uint64_t select_in_words(size_t first_word, size_t rank, bool value) const {
155 const size_t first_bit = first_word * kWordSize;
156 if (first_bit + kBasicBlockSize <= num_bits_ &&
157 first_word + kWordsPerBlock <= bits_.size()) {
158 return value ? first_bit + select_512(&bits_[first_word], rank - 1)
159 : first_bit + select0_512(&bits_[first_word], rank - 1);
160 }
161
162 for (size_t word_index = first_word; word_index < logical_word_count();
163 ++word_index) {
164 const uint64_t word = logical_word(word_index);
165 const uint64_t candidates =
166 value ? word
167 : (~word & first_bits_mask(logical_word_bits(word_index)));
168 const size_t count = std::popcount(candidates);
169 if (rank > count) {
170 rank -= count;
171 continue;
172 }
173 return word_index * kWordSize + select_64(candidates, rank - 1);
174 }
175 return num_bits_;
176 }
177
178 static size_t select_sample_count_for_rank(size_t rank_count) {
179 return 1 + rank_count / kSelectSampleFrequency;
180 }
181
182 static size_t select_sample_upper_bound(size_t bit_count) {
183 return select_sample_count_for_rank(bit_count);
184 }
185
186 struct SelectSampleWriter {
187 std::span<uint64_t> words;
188 size_t next = 0;
189 size_t count = 0;
190 size_t capacity = 0;
191 bool enabled = false;
192 bool reversed = false;
193
194 SelectSampleWriter() = default;
195
196 SelectSampleWriter(std::span<uint64_t> words,
197 size_t begin,
198 size_t capacity,
199 bool enabled,
200 bool reversed)
201 : words(words),
202 next(begin),
203 capacity(capacity),
204 enabled(enabled),
205 reversed(reversed) {}
206
207 void append(uint64_t sample) {
208 if (!enabled) {
209 return;
210 }
211 if (count >= capacity) [[unlikely]] {
212 throw std::invalid_argument(
213 "RankSelectSupport one_count hint is inconsistent with input bits");
214 }
215 words[next] = sample;
216 ++count;
217 if (reversed) {
218 if (next != 0) {
219 --next;
220 }
221 } else {
222 ++next;
223 }
224 }
225 };
226
227 struct SelectSampleWriters {
228 SelectSampleWriter ones;
229 SelectSampleWriter zeros;
230 bool shrink_after_build = false;
231 };
232
233 SelectSampleWriters initialize_select_sample_writers(
234 bool need_select1,
235 bool need_select0,
236 std::optional<size_t> one_count) {
237 select1_sample_begin_ = 0;
238 select1_sample_count_ = 0;
239 select0_sample_begin_ = 0;
240 select0_sample_count_ = 0;
241 select0_samples_reversed_ = false;
242 select_samples_.resize(0);
243
244 SelectSampleWriters writers;
245 if (!need_select1 && !need_select0) {
246 return writers;
247 }
248
249 const std::optional<size_t> zero_count =
250 one_count ? std::optional<size_t>(num_bits_ - *one_count)
251 : std::nullopt;
252 if (need_select1 && need_select0) {
253 const size_t one_sample_capacity =
254 one_count ? select_sample_count_for_rank(*one_count)
255 : 2 + num_bits_ / kSelectSampleFrequency;
256 const size_t zero_sample_capacity =
257 zero_count ? select_sample_count_for_rank(*zero_count)
258 : 2 + num_bits_ / kSelectSampleFrequency;
259 const size_t total_samples =
260 one_count ? one_sample_capacity + zero_sample_capacity
261 : 2 + num_bits_ / kSelectSampleFrequency;
262 select_samples_.resize(total_samples * kWordSize);
263 auto samples = select_samples_.writable_words64();
264 select1_sample_begin_ = 0;
265 select0_samples_reversed_ = true;
266 writers.ones =
267 SelectSampleWriter(samples, 0, one_sample_capacity, true, false);
268 writers.zeros = SelectSampleWriter(samples, total_samples - 1,
269 zero_sample_capacity, true, true);
270 writers.ones.append(0);
271 writers.zeros.append(0);
272 return writers;
273 }
274
275 const size_t sample_capacity =
276 need_select1 ? (one_count ? select_sample_count_for_rank(*one_count)
277 : select_sample_upper_bound(num_bits_))
278 : (zero_count ? select_sample_count_for_rank(*zero_count)
279 : select_sample_upper_bound(num_bits_));
280 select_samples_.resize(sample_capacity * kWordSize);
281 auto samples = select_samples_.writable_words64();
282 writers.shrink_after_build = !one_count;
283 if (need_select1) {
284 select1_sample_begin_ = 0;
285 writers.ones =
286 SelectSampleWriter(samples, 0, sample_capacity, true, false);
287 writers.ones.append(0);
288 } else {
289 select0_sample_begin_ = 0;
290 writers.zeros =
291 SelectSampleWriter(samples, 0, sample_capacity, true, false);
292 writers.zeros.append(0);
293 }
294 return writers;
295 }
296
297 void finalize_select_sample_writers(SelectSampleWriters writers) {
298 select1_sample_count_ = writers.ones.count;
299 select0_sample_count_ = writers.zeros.count;
300 if (writers.zeros.reversed) {
301 select0_sample_begin_ = writers.zeros.next + 1;
302 auto zero_samples = writers.zeros.words.subspan(select0_sample_begin_,
303 select0_sample_count_);
304 std::reverse(zero_samples.begin(), zero_samples.end());
305 select0_samples_reversed_ = false;
306 }
307 if (writers.shrink_after_build) {
308 const size_t sample_count = select1_sample_count_ != 0
309 ? select1_sample_count_
310 : select0_sample_count_;
311 select_samples_.resize(sample_count * kWordSize);
312 select_samples_.shrink_to_fit();
313 }
314 }
315
316 uint64_t select1_sample(size_t sample_index) const {
317 auto samples = select_samples_.as_words64();
318 return samples[select1_sample_begin_ + sample_index];
319 }
320
321 uint64_t select0_sample(size_t sample_index) const {
322 auto samples = select_samples_.as_words64();
323 if (select0_samples_reversed_) {
324 return samples[select0_sample_begin_ + select0_sample_count_ - 1 -
325 sample_index];
326 }
327 return samples[select0_sample_begin_ + sample_index];
328 }
329
333 void build_rank_select(SelectSupport support,
334 std::optional<size_t> one_count) {
335 select_support_ = support;
336 size_t num_superblocks =
337 8 + (padded_size_ == 0 ? 0 : (padded_size_ - 1) / kSuperBlockSize);
338 // Add more blocks to ease SIMD processing
339 // num_basicblocks to fully cover superblock, i.e. 128
340 // This reduces branching in select
341 num_superblocks = ((num_superblocks + 7) / 8) * 8;
342 size_t num_basicblocks = num_superblocks * kBlocksPerSuperBlock;
343 super_block_rank_.resize(num_superblocks * 64);
344 basic_block_rank_.resize(num_basicblocks * 16);
345
346 auto super_block_rank = super_block_rank_.writable_words64();
347 auto basic_block_rank = basic_block_rank_.writable_words16();
348
349 const bool need_select1 = builds_select1(support);
350 const bool need_select0 = builds_select0(support);
351 if (one_count && *one_count > num_bits_) {
352 throw std::invalid_argument(
353 "RankSelectSupport one_count hint cannot exceed num_bits");
354 }
355 auto select_writers =
356 initialize_select_sample_writers(need_select1, need_select0, one_count);
357
358 uint64_t super_block_sum = 0;
359 uint64_t basic_block_sum = 0;
360 uint64_t milestone = kSelectSampleFrequency;
361 uint64_t milestone0 = kSelectSampleFrequency;
362 uint64_t rank = 0;
363 uint64_t rank0 = 0;
364
365 for (size_t i = 0; i / kBasicBlockSize < basic_block_rank.size();
366 i += kWordSize) {
367 if (i % kSuperBlockSize == 0) {
368 super_block_sum += basic_block_sum;
369 super_block_rank[i / kSuperBlockSize] = super_block_sum;
370 basic_block_sum = 0;
371 }
372 if (i % kBasicBlockSize == 0) {
373 basic_block_rank[i / kBasicBlockSize] =
374 static_cast<uint16_t>(basic_block_sum);
375 }
376 if (i / kWordSize < logical_word_count()) {
377 const size_t word_index = i / kWordSize;
378 const uint64_t word = logical_word(word_index);
379 const size_t word_bits = logical_word_bits(word_index);
380 const uint64_t ones = std::popcount(word);
381 const uint64_t zeros = word_bits - ones;
382 if (need_select1 && rank + ones >= milestone) {
383 const auto pos = select_64(word, milestone - rank - 1);
384 // TODO: try including global rank into select samples to save
385 // a cache miss on global rank scan
386 select_writers.ones.append((64 * word_index + pos) / kSuperBlockSize);
387 milestone += kSelectSampleFrequency;
388 }
389 if (need_select0 && rank0 + zeros >= milestone0) {
390 const uint64_t zero_word = ~word & first_bits_mask(word_bits);
391 const auto pos = select_64(zero_word, milestone0 - rank0 - 1);
392 select_writers.zeros.append((64 * word_index + pos) /
393 kSuperBlockSize);
394 milestone0 += kSelectSampleFrequency;
395 }
396 basic_block_sum += ones;
397 rank += ones;
398 rank0 += zeros;
399 }
400 }
401 max_rank_ = super_block_sum + basic_block_sum;
402 finalize_select_sample_writers(select_writers);
403
404 for (size_t i = 0; i < 8; ++i) {
405 delta_super[i] = i * kSuperBlockSize;
406 }
407 for (size_t i = 0; i < 32; ++i) {
408 delta_basic[i] = i * kBasicBlockSize;
409 }
410 }
411
417 uint64_t find_superblock(uint64_t rank) const {
418 auto super_block_rank = super_block_rank_.as_words64();
419
420 uint64_t left = select1_sample(rank / kSelectSampleFrequency);
421
422 while (left + 7 < super_block_rank.size()) {
423 auto len = lower_bound_8x64(&super_block_rank[left], rank);
424 if (len < 8) {
425 return left + len - 1;
426 }
427 left += 8;
428 }
429 if (left + 3 < super_block_rank.size()) {
430 auto len = lower_bound_4x64(&super_block_rank[left], rank);
431 if (len < 4) {
432 return left + len - 1;
433 }
434 left += 4;
435 }
436 while (left < super_block_rank.size() && super_block_rank[left] < rank) {
437 left++;
438 }
439 return left - 1;
440 }
441
447 uint64_t find_superblock_zeros(uint64_t rank0) const {
448 auto super_block_rank = super_block_rank_.as_words64();
449
450 uint64_t left = select0_sample(rank0 / kSelectSampleFrequency);
451
452 while (left + 7 < super_block_rank.size()) {
453 auto len = lower_bound_delta_8x64(&super_block_rank[left], rank0,
454 delta_super, kSuperBlockSize * left);
455 if (len < 8) {
456 return left + len - 1;
457 }
458 left += 8;
459 }
460 if (left + 3 < super_block_rank.size()) {
461 auto len = lower_bound_delta_4x64(&super_block_rank[left], rank0,
462 delta_super, kSuperBlockSize * left);
463 if (len < 4) {
464 return left + len - 1;
465 }
466 left += 4;
467 }
468 while (left < super_block_rank.size() &&
469 kSuperBlockSize * left - super_block_rank[left] < rank0) {
470 left++;
471 }
472 return left - 1;
473 }
474
486 uint64_t find_basicblock(uint16_t local_rank, uint64_t s_block) const {
487 auto basic_block_rank = basic_block_rank_.as_words16();
488
489 for (size_t pos = 0; pos < kBlocksPerSuperBlock; pos += 32) {
490 auto count = lower_bound_32x16(
491 &basic_block_rank[kBlocksPerSuperBlock * s_block + pos], local_rank);
492 if (count < 32) {
493 return kBlocksPerSuperBlock * s_block + pos + count - 1;
494 }
495 }
496 return kBlocksPerSuperBlock * s_block + kBlocksPerSuperBlock - 1;
497 }
498
510 uint64_t find_basicblock_zeros(uint16_t local_rank0, uint64_t s_block) const {
511 auto basic_block_rank = basic_block_rank_.as_words16();
512 for (size_t pos = 0; pos < kBlocksPerSuperBlock; pos += 32) {
513 auto count = lower_bound_delta_32x16(
514 &basic_block_rank[kBlocksPerSuperBlock * s_block + pos], local_rank0,
515 delta_basic, kBasicBlockSize * pos);
516 if (count < 32) {
517 return kBlocksPerSuperBlock * s_block + pos + count - 1;
518 }
519 }
520 return kBlocksPerSuperBlock * s_block + kBlocksPerSuperBlock - 1;
521 }
522
540 uint64_t find_basicblock_is(uint16_t local_rank, uint64_t s_block) const {
541 auto super_block_rank = super_block_rank_.as_words64();
542 auto basic_block_rank = basic_block_rank_.as_words16();
543
544 auto lower = super_block_rank[s_block];
545 auto upper = super_block_rank[s_block + 1];
546
547 uint64_t pos = kBlocksPerSuperBlock * local_rank / (upper - lower);
548 pos = pos + 16 < 32 ? 0 : (pos - 16);
549 pos = pos > 96 ? 96 : pos;
550 while (pos < 96) {
551 auto count = lower_bound_32x16(
552 &basic_block_rank[kBlocksPerSuperBlock * s_block + pos], local_rank);
553 if (count == 0) {
554 return find_basicblock(local_rank, s_block);
555 }
556 if (count < 32) {
557 return kBlocksPerSuperBlock * s_block + pos + count - 1;
558 }
559 pos += 32;
560 }
561 pos = 96;
562 auto count = lower_bound_32x16(
563 &basic_block_rank[kBlocksPerSuperBlock * s_block + pos], local_rank);
564 if (count == 0) {
565 return find_basicblock(local_rank, s_block);
566 }
567 return kBlocksPerSuperBlock * s_block + pos + count - 1;
568 }
569
579 uint64_t find_basicblock_is_zeros(uint16_t local_rank0,
580 uint64_t s_block) const {
581 auto super_block_rank = super_block_rank_.as_words64();
582 auto basic_block_rank = basic_block_rank_.as_words16();
583
584 auto lower = kSuperBlockSize * s_block - super_block_rank[s_block];
585 auto upper =
586 kSuperBlockSize * (s_block + 1) - super_block_rank[s_block + 1];
587
588 uint64_t interpolation =
589 kBlocksPerSuperBlock * local_rank0 / (upper - lower);
590 // Random data usually places the interpolation estimate in the target
591 // block. Validate it from existing one-prefix metadata before the SIMD
592 // derived-zero scan.
593 const uint64_t block_offset =
594 std::min(interpolation, kBlocksPerSuperBlock - 1);
595 const uint64_t block = kBlocksPerSuperBlock * s_block + block_offset;
596 const uint64_t zero_before =
597 kBasicBlockSize * block_offset - basic_block_rank[block];
598 const uint64_t zero_after = block_offset + 1 == kBlocksPerSuperBlock
599 ? upper - lower
600 : kBasicBlockSize * (block_offset + 1) -
601 basic_block_rank[block + 1];
602 if (zero_before < local_rank0 && local_rank0 <= zero_after) {
603 return block;
604 }
605
606 uint64_t pos = interpolation;
607 pos = pos + 16 < 32 ? 0 : (pos - 16);
608 pos = pos > 96 ? 96 : pos;
609 while (pos < 96) {
610 auto count = lower_bound_delta_32x16(
611 &basic_block_rank[kBlocksPerSuperBlock * s_block + pos], local_rank0,
612 delta_basic, kBasicBlockSize * pos);
613 if (count == 0) {
614 return find_basicblock_zeros(local_rank0, s_block);
615 }
616 if (count < 32) {
617 return kBlocksPerSuperBlock * s_block + pos + count - 1;
618 }
619 pos += 32;
620 }
621 pos = 96;
622 auto count = lower_bound_delta_32x16(
623 &basic_block_rank[kBlocksPerSuperBlock * s_block + pos], local_rank0,
624 delta_basic, kBasicBlockSize * pos);
625 if (count == 0) {
626 return find_basicblock_zeros(local_rank0, s_block);
627 }
628 return kBlocksPerSuperBlock * s_block + pos + count - 1;
629 }
630
631 public:
632 RankSelectSupport() = default;
633 RankSelectSupport(const RankSelectSupport&) = default;
634 RankSelectSupport(RankSelectSupport&&) noexcept = default;
635 RankSelectSupport& operator=(const RankSelectSupport&) = default;
636 RankSelectSupport& operator=(RankSelectSupport&&) noexcept = default;
637
638#ifdef PIXIE_DIAGNOSTICS
639 struct DiagnosticsBytes {
640 size_t source_bit_sequence_bytes = 0;
641 size_t super_block_rank_bytes = 0;
642 size_t basic_block_rank_bytes = 0;
643 size_t select1_samples_bytes = 0;
644 size_t select0_samples_bytes = 0;
645 size_t total_bytes = 0;
646 };
647
651 DiagnosticsBytes diagnostics_bytes() const {
652 DiagnosticsBytes result;
653 result.source_bit_sequence_bytes = (num_bits_ + 7) / 8;
654 result.super_block_rank_bytes = super_block_rank_.as_bytes().size();
655 result.basic_block_rank_bytes = basic_block_rank_.as_bytes().size();
656 result.select1_samples_bytes = select1_sample_count_ * sizeof(uint64_t);
657 result.select0_samples_bytes = select0_sample_count_ * sizeof(uint64_t);
658 result.total_bytes = result.super_block_rank_bytes +
659 result.basic_block_rank_bytes +
660 select_samples_.as_bytes().size();
661 return result;
662 }
663
667 void memory_report() const {
668 const auto diagnostics = diagnostics_bytes();
669 const double source_bytes =
670 static_cast<double>(diagnostics.source_bit_sequence_bytes);
671 const auto log_bytes = [&](std::string_view label, size_t bytes) {
672 const double percentage =
673 source_bytes > 0.0 ? 100.0 * static_cast<double>(bytes) / source_bytes
674 : 0.0;
675 spdlog::info("RankSelectSupport {}: {} bytes ({:.2f}% of source)", label,
676 bytes, percentage);
677 };
678 log_bytes("source_bit_sequence", diagnostics.source_bit_sequence_bytes);
679 log_bytes("super_block_rank", diagnostics.super_block_rank_bytes);
680 log_bytes("basic_block_rank", diagnostics.basic_block_rank_bytes);
681 log_bytes("select1_samples", diagnostics.select1_samples_bytes);
682 log_bytes("select0_samples", diagnostics.select0_samples_bytes);
683 log_bytes("total", diagnostics.total_bytes);
684 }
685#endif
696 ReadOnlyStorageView source_storage,
697 size_t num_bits,
698 SelectSupport select_support = SelectSupport::kBoth,
699 std::optional<size_t> one_count = std::nullopt)
700 : source_storage_(source_storage),
701 bits_(source_storage_.as_words64()),
702 num_bits_(std::min(num_bits, bits_.size() * kWordSize)),
703 padded_size_(((num_bits_ + kWordSize - 1) / kWordSize) * kWordSize) {
704 build_rank_select(select_support, one_count);
705 }
706
715 std::span<const uint64_t> source_words,
716 size_t num_bits,
717 SelectSupport select_support = SelectSupport::kBoth,
718 std::optional<size_t> one_count = std::nullopt)
719 : RankSelectSupport(ReadOnlyStorageView(std::as_bytes(source_words)),
720 num_bits,
721 select_support,
722 one_count) {}
723
733 template <StorageImplementation SourceStorage>
735 const SourceStorage& source_storage,
736 size_t num_bits,
737 SelectSupport select_support = SelectSupport::kBoth,
738 std::optional<size_t> one_count = std::nullopt)
739 : RankSelectSupport(source_storage.view(),
740 num_bits,
741 select_support,
742 one_count) {}
743
747 size_t size_impl() const { return num_bits_; }
748
752 bool supports_select1_impl() const { return builds_select1(select_support_); }
753
757 bool supports_select0_impl() const { return builds_select0(select_support_); }
758
766 requires requires(const MetadataStorage& storage) {
767 storage.allocated_bytes();
768 }
769 {
770 return sizeof(*this) + super_block_rank_.allocated_bytes() +
771 basic_block_rank_.allocated_bytes() +
772 select_samples_.allocated_bytes();
773 }
774
780 int bit_impl(size_t pos) const {
781 size_t word_idx = pos / kWordSize;
782 size_t bit_off = pos % kWordSize;
783
784 return (bits_[word_idx] >> bit_off) & 1;
785 }
786
793 uint64_t rank_impl(size_t pos) const {
794 if (pos >= num_bits_) [[unlikely]] {
795 return max_rank_;
796 }
797
798 auto super_block_rank = super_block_rank_.as_words64();
799 auto basic_block_rank = basic_block_rank_.as_words16();
800
801 uint64_t b_block = pos / kBasicBlockSize;
802 uint64_t s_block = pos / kSuperBlockSize;
803 // Precomputed rank
804 uint64_t result = super_block_rank[s_block] + basic_block_rank[b_block];
805 // Basic block tail
806 result += rank_in_basic_block(b_block, pos - (b_block * kBasicBlockSize));
807 return result;
808 }
809
816 uint64_t select_impl(size_t rank) const {
817 if (rank == 0) [[unlikely]] {
818 return 0;
819 }
820 if (!supports_select1_impl()) [[unlikely]] {
821 return num_bits_;
822 }
823 if (rank > max_rank_) [[unlikely]] {
824 return num_bits_;
825 }
826 auto super_block_rank = super_block_rank_.as_words64();
827 auto basic_block_rank = basic_block_rank_.as_words16();
828
829 uint64_t s_block = find_superblock(rank);
830 rank -= super_block_rank[s_block];
831 auto pos = find_basicblock_is(rank, s_block);
832 rank -= basic_block_rank[pos];
833 return select_in_words(pos * kWordsPerBlock, rank, true);
834 }
835
843 uint64_t select0_impl(size_t rank0) const {
844 if (rank0 == 0) [[unlikely]] {
845 return 0;
846 }
847 if (!supports_select0_impl()) [[unlikely]] {
848 return num_bits_;
849 }
850 if (rank0 > num_bits_ - max_rank_) [[unlikely]] {
851 return num_bits_;
852 }
853 auto super_block_rank = super_block_rank_.as_words64();
854 auto basic_block_rank = basic_block_rank_.as_words16();
855
856 uint64_t s_block = find_superblock_zeros(rank0);
857 rank0 -= kSuperBlockSize * s_block - super_block_rank[s_block];
858 auto pos = find_basicblock_is_zeros(rank0, s_block);
859 auto pos_in_super_block = pos & (kBlocksPerSuperBlock - 1);
860 rank0 -= kBasicBlockSize * pos_in_super_block - basic_block_rank[pos];
861 return select_in_words(pos * kWordsPerBlock, rank0, false);
862 }
863
864 void serialize(pixie::OutputBitStream& bs) const {
865 bs << num_bits_ << padded_size_ << max_rank_ << select1_sample_begin_
866 << select1_sample_count_ << select0_sample_begin_
867 << select0_sample_count_ << static_cast<uint32_t>(select_support_)
868 << static_cast<uint32_t>(select0_samples_reversed_);
869 for (const uint64_t delta : delta_super) {
870 bs << delta;
871 }
872 for (const uint16_t delta : delta_basic) {
873 bs << delta;
874 }
875 super_block_rank_.serialize(bs);
876 basic_block_rank_.serialize(bs);
877 select_samples_.serialize(bs);
878 }
879
880 static RankSelectSupport deserialize(std::span<const uint64_t> source_bits,
881 std::span<const std::byte>& data)
882 requires std::same_as<MetadataStorage, ReadOnlyStorageView>
883 {
884 RankSelectSupport result;
885 result.source_storage_ = ReadOnlyStorageView(std::as_bytes(source_bits));
886 result.bits_ = result.source_storage_.as_words64();
887 auto read = [&data](auto& value) {
888 constexpr size_t length = sizeof(value);
889 std::memcpy(&value, data.data(), length);
890 data = data.subspan(length);
891 };
892 read(result.num_bits_);
893 read(result.padded_size_);
894 read(result.max_rank_);
895 read(result.select1_sample_begin_);
896 read(result.select1_sample_count_);
897 read(result.select0_sample_begin_);
898 read(result.select0_sample_count_);
899 uint32_t buf;
900 read(buf);
901 result.select_support_ = static_cast<SelectSupport>(buf);
902 read(buf);
903 result.select0_samples_reversed_ = static_cast<bool>(buf);
904 for (uint64_t& delta : result.delta_super) {
905 read(delta);
906 }
907 for (uint16_t& delta : result.delta_basic) {
908 read(delta);
909 }
910 result.super_block_rank_ = ReadOnlyStorageView::deserialize(data);
911 result.basic_block_rank_ = ReadOnlyStorageView::deserialize(data);
912 result.select_samples_ = ReadOnlyStorageView::deserialize(data);
913 return result;
914 }
915};
916
917} // namespace pixie
Definition bit_stream.h:9
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:793
size_t memory_usage_bytes_impl() const
Return owned auxiliary memory usage in bytes.
Definition support.h:765
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:734
bool supports_select1_impl() const
Whether this index stores samples for select1 queries.
Definition support.h:752
bool supports_select0_impl() const
Whether this index stores samples for select0 queries.
Definition support.h:757
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:714
uint64_t select0_impl(size_t rank0) const
Select the position of the rank0-th 0-bit (1-indexed).
Definition support.h:843
SelectSupport
Select directions to index during construction.
Definition support.h:60
int bit_impl(size_t pos) const
Returns the bit at the given position.
Definition support.h:780
size_t size_impl() const
Returns the number of valid bits.
Definition support.h:747
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:695
uint64_t select_impl(size_t rank) const
Rank of 0s up to position pos (exclusive).
Definition support.h:816
A non-owning, read-only view of a byte sequence.
Definition read_only_view.h:17
static ReadOnlyStorageView deserialize(std::span< const std::byte > &data)
Deserialize a size-prefixed view and advance data.
Definition read_only_view.h:44
Common interface for rank/select support over packed bit sequences.