54class RankSelectSupport
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;
77 alignas(64) uint64_t delta_super[8]{};
78 alignas(64) uint16_t delta_basic[32]{};
80 MetadataStorage super_block_rank_;
81 MetadataStorage basic_block_rank_;
82 MetadataStorage select_samples_;
83 ReadOnlyStorageView source_storage_;
84 std::span<const uint64_t> bits_;
86 size_t padded_size_{};
88 size_t select1_sample_begin_{};
89 size_t select1_sample_count_{};
90 size_t select0_sample_begin_{};
91 size_t select0_sample_count_{};
93 bool select0_samples_reversed_ =
false;
96 return (
static_cast<uint8_t
>(support) &
97 static_cast<uint8_t
>(SelectSupport::kSelect1)) != 0;
101 return (
static_cast<uint8_t
>(support) &
102 static_cast<uint8_t
>(SelectSupport::kSelect0)) != 0;
105 size_t logical_word_count()
const {
106 return (num_bits_ + kWordSize - 1) / kWordSize;
109 size_t logical_word_bits(
size_t word_index)
const {
110 const size_t begin = word_index * kWordSize;
111 if (begin >= num_bits_) {
114 return std::min(kWordSize, num_bits_ - begin);
117 uint64_t logical_word(
size_t word_index)
const {
118 if (word_index >= bits_.size()) {
121 const size_t bits = logical_word_bits(word_index);
125 if (bits == kWordSize) {
126 return bits_[word_index];
128 return bits_[word_index] & first_bits_mask(bits);
131 uint64_t rank_in_basic_block(
size_t basic_block,
size_t offset)
const {
135 const size_t first_word = basic_block * kWordsPerBlock;
136 if (first_word + kWordsPerBlock <= bits_.size()) {
137 return rank_512(&bits_[first_word], offset);
141 size_t word_index = first_word;
142 while (offset >= kWordSize) {
143 result += std::popcount(logical_word(word_index));
149 std::popcount(logical_word(word_index) & first_bits_mask(offset));
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);
162 for (
size_t word_index = first_word; word_index < logical_word_count();
164 const uint64_t word = logical_word(word_index);
165 const uint64_t candidates =
167 : (~word & first_bits_mask(logical_word_bits(word_index)));
168 const size_t count = std::popcount(candidates);
173 return word_index * kWordSize + select_64(candidates,
rank - 1);
178 static size_t select_sample_count_for_rank(
size_t rank_count) {
179 return 1 + rank_count / kSelectSampleFrequency;
182 static size_t select_sample_upper_bound(
size_t bit_count) {
183 return select_sample_count_for_rank(bit_count);
186 struct SelectSampleWriter {
187 std::span<uint64_t> words;
191 bool enabled =
false;
192 bool reversed =
false;
194 SelectSampleWriter() =
default;
196 SelectSampleWriter(std::span<uint64_t> words,
205 reversed(reversed) {}
207 void append(uint64_t sample) {
211 if (count >= capacity) [[unlikely]] {
212 throw std::invalid_argument(
213 "RankSelectSupport one_count hint is inconsistent with input bits");
215 words[next] = sample;
227 struct SelectSampleWriters {
228 SelectSampleWriter ones;
229 SelectSampleWriter zeros;
230 bool shrink_after_build =
false;
233 SelectSampleWriters initialize_select_sample_writers(
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);
244 SelectSampleWriters writers;
245 if (!need_select1 && !need_select0) {
249 const std::optional<size_t> zero_count =
250 one_count ? std::optional<size_t>(num_bits_ - *one_count)
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;
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);
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;
284 select1_sample_begin_ = 0;
286 SelectSampleWriter(samples, 0, sample_capacity,
true,
false);
287 writers.ones.append(0);
289 select0_sample_begin_ = 0;
291 SelectSampleWriter(samples, 0, sample_capacity,
true,
false);
292 writers.zeros.append(0);
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;
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();
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];
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 -
327 return samples[select0_sample_begin_ + sample_index];
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);
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);
346 auto super_block_rank = super_block_rank_.writable_words64();
347 auto basic_block_rank = basic_block_rank_.writable_words16();
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");
355 auto select_writers =
356 initialize_select_sample_writers(need_select1, need_select0, one_count);
358 uint64_t super_block_sum = 0;
359 uint64_t basic_block_sum = 0;
360 uint64_t milestone = kSelectSampleFrequency;
361 uint64_t milestone0 = kSelectSampleFrequency;
365 for (
size_t i = 0; i / kBasicBlockSize < basic_block_rank.size();
367 if (i % kSuperBlockSize == 0) {
368 super_block_sum += basic_block_sum;
369 super_block_rank[i / kSuperBlockSize] = super_block_sum;
372 if (i % kBasicBlockSize == 0) {
373 basic_block_rank[i / kBasicBlockSize] =
374 static_cast<uint16_t
>(basic_block_sum);
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);
386 select_writers.ones.append((64 * word_index + pos) / kSuperBlockSize);
387 milestone += kSelectSampleFrequency;
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) /
394 milestone0 += kSelectSampleFrequency;
396 basic_block_sum += ones;
401 max_rank_ = super_block_sum + basic_block_sum;
402 finalize_select_sample_writers(select_writers);
404 for (
size_t i = 0; i < 8; ++i) {
405 delta_super[i] = i * kSuperBlockSize;
407 for (
size_t i = 0; i < 32; ++i) {
408 delta_basic[i] = i * kBasicBlockSize;
417 uint64_t find_superblock(uint64_t
rank)
const {
418 auto super_block_rank = super_block_rank_.as_words64();
420 uint64_t left = select1_sample(
rank / kSelectSampleFrequency);
422 while (left + 7 < super_block_rank.size()) {
423 auto len = lower_bound_8x64(&super_block_rank[left],
rank);
425 return left + len - 1;
429 if (left + 3 < super_block_rank.size()) {
430 auto len = lower_bound_4x64(&super_block_rank[left],
rank);
432 return left + len - 1;
436 while (left < super_block_rank.size() && super_block_rank[left] <
rank) {
447 uint64_t find_superblock_zeros(uint64_t
rank0)
const {
448 auto super_block_rank = super_block_rank_.as_words64();
450 uint64_t left = select0_sample(
rank0 / kSelectSampleFrequency);
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);
456 return left + len - 1;
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);
464 return left + len - 1;
468 while (left < super_block_rank.size() &&
469 kSuperBlockSize * left - super_block_rank[left] <
rank0) {
486 uint64_t find_basicblock(uint16_t local_rank, uint64_t s_block)
const {
487 auto basic_block_rank = basic_block_rank_.as_words16();
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);
493 return kBlocksPerSuperBlock * s_block + pos + count - 1;
496 return kBlocksPerSuperBlock * s_block + kBlocksPerSuperBlock - 1;
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);
517 return kBlocksPerSuperBlock * s_block + pos + count - 1;
520 return kBlocksPerSuperBlock * s_block + kBlocksPerSuperBlock - 1;
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();
544 auto lower = super_block_rank[s_block];
545 auto upper = super_block_rank[s_block + 1];
547 uint64_t pos = kBlocksPerSuperBlock * local_rank / (upper - lower);
548 pos = pos + 16 < 32 ? 0 : (pos - 16);
549 pos = pos > 96 ? 96 : pos;
551 auto count = lower_bound_32x16(
552 &basic_block_rank[kBlocksPerSuperBlock * s_block + pos], local_rank);
554 return find_basicblock(local_rank, s_block);
557 return kBlocksPerSuperBlock * s_block + pos + count - 1;
562 auto count = lower_bound_32x16(
563 &basic_block_rank[kBlocksPerSuperBlock * s_block + pos], local_rank);
565 return find_basicblock(local_rank, s_block);
567 return kBlocksPerSuperBlock * s_block + pos + count - 1;
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();
584 auto lower = kSuperBlockSize * s_block - super_block_rank[s_block];
586 kSuperBlockSize * (s_block + 1) - super_block_rank[s_block + 1];
588 uint64_t interpolation =
589 kBlocksPerSuperBlock * local_rank0 / (upper - lower);
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
600 : kBasicBlockSize * (block_offset + 1) -
601 basic_block_rank[block + 1];
602 if (zero_before < local_rank0 && local_rank0 <= zero_after) {
606 uint64_t pos = interpolation;
607 pos = pos + 16 < 32 ? 0 : (pos - 16);
608 pos = pos > 96 ? 96 : pos;
610 auto count = lower_bound_delta_32x16(
611 &basic_block_rank[kBlocksPerSuperBlock * s_block + pos], local_rank0,
612 delta_basic, kBasicBlockSize * pos);
614 return find_basicblock_zeros(local_rank0, s_block);
617 return kBlocksPerSuperBlock * s_block + pos + count - 1;
622 auto count = lower_bound_delta_32x16(
623 &basic_block_rank[kBlocksPerSuperBlock * s_block + pos], local_rank0,
624 delta_basic, kBasicBlockSize * pos);
626 return find_basicblock_zeros(local_rank0, s_block);
628 return kBlocksPerSuperBlock * s_block + pos + count - 1;
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;
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;
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();
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
675 spdlog::info(
"RankSelectSupport {}: {} bytes ({:.2f}% of source)", label,
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);
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);
715 std::span<const uint64_t> source_words,
718 std::optional<size_t> one_count = std::nullopt)
733 template <StorageImplementation SourceStorage>
735 const SourceStorage& source_storage,
738 std::optional<size_t> one_count = std::nullopt)
739 : RankSelectSupport(source_storage.view(),
766 requires requires(const MetadataStorage& storage) {
767 storage.allocated_bytes();
770 return sizeof(*this) + super_block_rank_.allocated_bytes() +
771 basic_block_rank_.allocated_bytes() +
772 select_samples_.allocated_bytes();
781 size_t word_idx = pos / kWordSize;
782 size_t bit_off = pos % kWordSize;
784 return (bits_[word_idx] >> bit_off) & 1;
794 if (pos >= num_bits_) [[unlikely]] {
798 auto super_block_rank = super_block_rank_.as_words64();
799 auto basic_block_rank = basic_block_rank_.as_words16();
801 uint64_t b_block = pos / kBasicBlockSize;
802 uint64_t s_block = pos / kSuperBlockSize;
804 uint64_t result = super_block_rank[s_block] + basic_block_rank[b_block];
806 result += rank_in_basic_block(b_block, pos - (b_block * kBasicBlockSize));
817 if (
rank == 0) [[unlikely]] {
823 if (
rank > max_rank_) [[unlikely]] {
826 auto super_block_rank = super_block_rank_.as_words64();
827 auto basic_block_rank = basic_block_rank_.as_words16();
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);
844 if (
rank0 == 0) [[unlikely]] {
850 if (
rank0 > num_bits_ - max_rank_) [[unlikely]] {
853 auto super_block_rank = super_block_rank_.as_words64();
854 auto basic_block_rank = basic_block_rank_.as_words16();
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);
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) {
872 for (
const uint16_t delta : delta_basic) {
875 super_block_rank_.serialize(bs);
876 basic_block_rank_.serialize(bs);
877 select_samples_.serialize(bs);
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>
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);
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_);
903 result.select0_samples_reversed_ =
static_cast<bool>(buf);
904 for (uint64_t& delta : result.delta_super) {
907 for (uint16_t& delta : result.delta_basic) {