75 :
public RankSelectBase<RankSelectSupportCompressed<MetadataStorage,
78 static_assert(std::has_single_bit(SampleFrequency));
79 static_assert(SearchThreshold > 0);
80 static_assert(std::is_nothrow_move_constructible_v<MetadataStorage> &&
81 std::is_nothrow_move_assignable_v<MetadataStorage>,
82 "Compressed metadata storage must have nonthrowing moves");
83 static constexpr bool kNothrowDefaultConstruction =
84 std::is_nothrow_default_constructible_v<MetadataStorage>;
104 kNothrowDefaultConstruction) {
114 if (
this != &other) {
127 kNothrowDefaultConstruction) {
128 if (
this != &other) {
148 std::span<const std::uint64_t> source_words,
149 std::size_t num_bits,
152 : bits_(source_words),
154 select_support_(select_support) {
155 if (
static_cast<std::uint8_t
>(select_support) > 3) {
156 throw std::invalid_argument(
"Invalid compressed select support mode");
158 if (source_words.size() <= std::numeric_limits<std::size_t>::max() / 64) {
159 num_bits_ = std::min(num_bits_, source_words.size() * 64);
163 select1_ = build_samples<false>();
166 select0_ = build_samples<true>();
175 return (bits_[position / 64] >> (position % 64)) & 1;
180#if __has_cpp_attribute(gnu::always_inline)
181 [[gnu::always_inline]]
184 if (end >= num_bits_) {
187 const std::size_t super = end / kSuperBits;
188 const std::size_t basic = (end % kSuperBits) / kBasicBits;
189 const auto* record = rank_record(super);
190 std::uint64_t result = record[0] + local_rank(record, basic);
191 const std::size_t first_word = (end / kBasicBits) * 8;
192 const std::size_t offset = end % kBasicBits;
196 if (bits_.size() - first_word >= 8) {
197 return result + ::rank_512(bits_.data() + first_word, offset);
199 const std::size_t last_word = end / 64;
200 for (std::size_t word = first_word; word < last_word; ++word) {
201 result += std::popcount(bits_[word]);
204 result += std::popcount(bits_[last_word] & ::first_bits_mask(end % 64));
211 if (end >= num_bits_) {
212 return num_bits_ - one_count_;
235 return (
static_cast<std::uint8_t
>(select_support_) & 1) != 0;
240 return (
static_cast<std::uint8_t
>(select_support_) & 2) != 0;
245 requires requires(const MetadataStorage& storage) {
246 storage.allocated_bytes();
249 return sizeof(*this) + ranks_.allocated_bytes() +
250 select1_.allocated_bytes() + select0_.allocated_bytes();
254 static constexpr std::size_t kSuperBits = 16384;
255 static constexpr std::size_t kBasicBits = 512;
256 static constexpr std::size_t kRecordBytes = 64;
259 static constexpr std::size_t kContiguous =
260 std::size_t{1} << (std::numeric_limits<std::size_t>::digits - 1);
266 using PrimarySamples = detail::compressed::BitmapMonotoneSamples<
267 detail::compressed::BlockedMonotoneSamples<MetadataStorage>>;
269 using PositionSamples = detail::compressed::PositionSamples<MetadataStorage>;
271 using MetadataArray = detail::compressed::MetadataArray<T, MetadataStorage>;
274 enum class Shape : std::uint8_t {
280 using Endpoint = std::conditional_t<
281 (SampleFrequency - 1 <= std::numeric_limits<std::uint16_t>::max()),
283 std::conditional_t<(SampleFrequency - 1 <=
284 std::numeric_limits<std::uint32_t>::max()),
287 PrimarySamples samples;
288 PositionSamples positions;
289 bool predicted =
false;
290 detail::compressed::BlockedMonotoneSamples<MetadataStorage> occupied;
291 MetadataArray<WideEntry> wide;
292 MetadataArray<Endpoint> endpoints;
295 std::size_t allocated_bytes()
const {
296 return samples.allocated_bytes() + positions.allocated_bytes() +
297 occupied.allocated_bytes() + wide.allocated_bytes() +
298 endpoints.allocated_bytes();
302 std::span<const std::uint64_t> bits_;
303 std::size_t num_bits_ = 0;
304 std::uint64_t one_count_ = 0;
305 MetadataArray<std::uint64_t> ranks_;
306 SelectIndex select1_;
307 SelectIndex select0_;
312 swap(bits_, other.bits_);
313 swap(num_bits_, other.num_bits_);
314 swap(one_count_, other.one_count_);
315 swap(ranks_, other.ranks_);
316 swap(select1_, other.select1_);
317 swap(select0_, other.select0_);
318 swap(select_support_, other.select_support_);
321 std::size_t super_count()
const {
return ranks_.size() / (kRecordBytes / 8); }
323 const std::uint64_t* rank_record(std::size_t super)
const {
324 return ranks_.data() + super * (kRecordBytes / 8);
327 static std::uint64_t local_rank(
const std::uint64_t* record,
328 std::size_t blocks) {
334 const auto terminal = record[7] >> 50;
337 return terminal != 0 ? terminal : (record[1] & 0x3fff) << 5;
339 const std::size_t bit = (blocks - 1) * 14;
340 constexpr auto mask = 0x3fffu;
341 if constexpr (std::endian::native == std::endian::little) {
342 std::uint32_t packed;
346 reinterpret_cast<const std::byte*
>(record + 1) + bit / 8,
348 return (packed >> (bit % 8)) & mask;
350 auto packed = record[1 + bit / 64] >> (bit % 64);
351 constexpr unsigned width = 14;
352 if (bit % 64 + width > 64) {
353 packed |= record[2 + bit / 64] << (64 - bit % 64);
355 return packed & mask;
359 static void write_local_rank(std::uint64_t* record,
361 std::uint64_t value) {
362 const std::size_t bit = (blocks - 1) * 14;
363 constexpr unsigned width = 14;
365 record[1 + bit / 64] |= value << (bit % 64);
366 if (bit % 64 + width > 64) {
367 record[2 + bit / 64] |= value >> (64 - bit % 64);
371 std::size_t super_size(std::size_t super)
const {
372 return std::min(kSuperBits, num_bits_ - super * kSuperBits);
376 const std::size_t count =
377 num_bits_ == 0 ? 0 : 1 + (num_bits_ - 1) / kSuperBits;
378 ranks_ = MetadataArray<std::uint64_t>(count * (kRecordBytes / 8));
379 auto* words = ranks_.data();
380 for (std::size_t super = 0; super < count; ++super) {
381 auto* record = words + super * (kRecordBytes / 8);
382 record[0] = one_count_;
383 const std::size_t base = super * kSuperBits;
384 const std::size_t valid = super_size(super);
385 std::uint64_t local = 0;
387 for (std::size_t basic = 0; basic < 32; ++basic) {
388 for (std::size_t lane = 0; lane < 8; ++lane) {
389 const std::size_t offset = basic * kBasicBits + lane * 64;
390 if (offset >= valid) {
393 const auto word = bits_[base / 64 + offset / 64];
394 local += std::popcount(word & ::first_bits_mask(std::min<std::size_t>(
395 64, valid - offset)));
398 write_local_rank(record, basic + 1, local);
406 std::uint64_t prefix_before(std::size_t super)
const {
407 const std::uint64_t ones = rank_record(super)[0];
408 if constexpr (Zero) {
409 return super * kSuperBits - ones;
415 std::uint64_t prefix_after(std::size_t super)
const {
416 const auto* record = rank_record(super);
417 const std::uint64_t ones = record[0] + local_rank(record, 32);
418 if constexpr (Zero) {
419 return super * kSuperBits + super_size(super) - ones;
425 SelectIndex build_samples()
const {
426 const std::uint64_t total = Zero ? num_bits_ - one_count_ : one_count_;
431 if (num_bits_ <= 1024) {
437 const std::size_t sample_count = 1 + (total - 1) / SampleFrequency;
438 std::vector<std::uint64_t> samples;
439 if (sample_count == std::numeric_limits<std::size_t>::max()) {
440 throw std::length_error(
"Too many select samples");
442 samples.reserve(sample_count + 1);
443 std::size_t last_occupied = 0;
444 [[maybe_unused]]
bool homogeneous =
true;
445 for (std::size_t super = 0; super < super_count(); ++super) {
446 const auto end = prefix_after<Zero>(super);
447 const auto before = prefix_before<Zero>(super);
448 if constexpr (SampleFrequency <= kSuperBits) {
449 homogeneous &= end == before || end - before == super_size(super);
452 last_occupied = super;
454 while (samples.size() < sample_count &&
455 samples.size() * SampleFrequency < end) {
456 samples.push_back(super);
459 samples.push_back(last_occupied + 1);
462 if constexpr (SampleFrequency <= kSuperBits) {
464 result.samples = PrimarySamples(samples);
465 result.shape = SelectIndex::Shape::kHomogeneous;
469 std::vector<std::uint64_t> occupied;
470 std::vector<std::uint64_t> wide_samples;
471 std::vector<WideEntry> wide;
472 std::vector<typename SelectIndex::Endpoint> endpoints;
473 bool needs_occupied_ids =
false;
474 for (std::size_t sample = 0; sample < sample_count; ++sample) {
475 if (samples[sample + 1] - samples[sample] < SearchThreshold) {
478 const std::uint64_t begin_rank = sample * SampleFrequency;
479 const std::uint64_t end_rank =
481 std::min<std::uint64_t>(SampleFrequency, total - begin_rank);
482 const std::size_t begin = occupied.size();
483 const std::size_t last =
484 std::min<std::size_t>(samples[sample + 1], super_count() - 1);
485 for (std::size_t super = samples[sample]; super <= last; ++super) {
486 const auto before = prefix_before<Zero>(super);
487 const auto after = prefix_after<Zero>(super);
488 if (after > before && after > begin_rank && before < end_rank) {
489 occupied.push_back(super);
490 endpoints.push_back(
static_cast<typename SelectIndex::Endpoint
>(
491 std::min<std::uint64_t>(after - begin_rank, SampleFrequency) -
495 const std::size_t count = occupied.size() - begin;
496 const bool contiguous = occupied.back() - occupied[begin] + 1 == count;
497 needs_occupied_ids |= !contiguous;
499 wide.push_back({begin, count | (contiguous ? kContiguous : 0)});
500 wide_samples.push_back(sample);
502 if (needs_occupied_ids) {
504 detail::compressed::BlockedMonotoneSamples<MetadataStorage>(occupied);
506 result.wide = MetadataArray<WideEntry>(wide);
507 result.endpoints = MetadataArray<typename SelectIndex::Endpoint>(endpoints);
509 const auto last_wide = wide_samples.empty() ? 0 : wide_samples.back();
510 result.shape = result.wide.empty() ? SelectIndex::Shape::kNarrow
511 : last_wide == result.wide.size() - 1
512 ? SelectIndex::Shape::kWidePrefix
513 : SelectIndex::Shape::kMixed;
515 if (result.shape != SelectIndex::Shape::kMixed) {
516 wide_samples.clear();
519 if (one_count_ >= num_bits_ / 16 &&
520 num_bits_ - one_count_ >= num_bits_ / 16) {
521 std::vector<std::uint64_t> positions;
522 positions.reserve(sample_count + 1);
523 for (std::size_t sample = 0; sample < sample_count; ++sample) {
524 positions.push_back(select_in_super<Zero>(sample * SampleFrequency + 1,
528 positions.push_back(select_in_super<Zero>(total, last_occupied) + 1);
529 result.positions = PositionSamples(positions, wide_samples);
530 result.predicted =
true;
532 result.samples = PrimarySamples(samples, wide_samples);
539 std::uint64_t select_direction(std::size_t
rank,
540 const SelectIndex& index,
541 bool enabled)
const {
545 const std::uint64_t total = Zero ? num_bits_ - one_count_ : one_count_;
546 if (!enabled ||
rank > total) {
550 if (num_bits_ <= 1024) {
551 return total == num_bits_ ?
rank - 1
552 : select_small_index<Zero>(
rank, total);
555 const std::size_t sample = (
rank - 1) / SampleFrequency;
556 if constexpr (SampleFrequency <= kSuperBits) {
557 if (index.shape == SelectIndex::Shape::kHomogeneous) {
561 const auto super = index.samples[sample];
562 return super * kSuperBits + ((
rank - 1) & (kSuperBits - 1));
565 std::uint64_t predicted_first = 0;
566 if (index.predicted) {
567 const auto [first, next] = index.positions.bounds(sample);
568 predicted_first = first / kSuperBits;
569 if (
const auto answer = predict<Zero>(
rank, first, next)) {
573 const WideEntry* entry =
nullptr;
574 switch (index.shape) {
575 case SelectIndex::Shape::kNarrow:
577 case SelectIndex::Shape::kWidePrefix:
578 if (sample < index.wide.size()) {
579 entry = &index.wide[sample];
582 case SelectIndex::Shape::kMixed:
583 if (
const auto mapped = index.predicted
584 ? index.positions.wide_entry(sample)
585 : index.samples.wide_entry(sample)) {
586 entry = &index.wide[*mapped];
589 case SelectIndex::Shape::kHomogeneous:
596 if (entry ==
nullptr) {
597 super = index.predicted ? predicted_first : index.samples[sample];
598 while (prefix_after<Zero>(super) <
rank) {
602 const std::size_t count = entry->count & ~kContiguous;
603 const auto relative_rank = (
rank - 1) % SampleFrequency;
606 const std::size_t offset =
608 : detail::compressed::grouped_lower_bound(
609 index.endpoints.data() + entry->begin, count,
611 assert(offset < count);
612 if ((entry->count & kContiguous) != 0) {
613 super = (index.predicted ? predicted_first : index.samples[sample]) +
616 super = index.occupied[entry->begin + offset];
620 return select_in_super<Zero>(
rank, super);
624 std::uint64_t select_in_super(std::size_t
rank, std::size_t super)
const {
625 const auto* record = rank_record(super);
626 const std::uint64_t target =
rank - prefix_before<Zero>(super);
627 const std::size_t valid = super_size(super);
628 const auto ones_local = local_rank(record, 32);
629 const std::uint64_t total_local = Zero ? valid - ones_local : ones_local;
630 if (total_local == valid) {
631 return super * kSuperBits + target - 1;
633 const std::size_t blocks = 1 + (valid - 1) / kBasicBits;
634 const auto packed_location =
635 detail::compressed::packed_prefix_search<Zero>(record, target, valid);
638 std::uint64_t before;
640 if (packed_location) {
641 basic = packed_location->basic;
642 before = packed_location->before;
643 after = packed_location->after;
645 basic = (target - 1) * blocks / total_local;
646 const auto count_at = [&](std::size_t boundary) {
647 const std::uint64_t ones = local_rank(record, boundary);
648 return Zero ? std::min(boundary * kBasicBits, valid) - ones : ones;
652 before = count_at(basic);
654 if (before >= target) {
657 before = count_at(--basic);
658 }
while (before >= target);
660 after = count_at(basic + 1);
661 while (after < target) {
663 after = count_at(++basic + 1);
667 const std::size_t base = super * kSuperBits + basic * kBasicBits;
668 return select_basic<Zero>(base, target - before, after - before);
672 std::uint64_t basic_prefix(std::size_t end)
const {
673 if constexpr (Zero) {
681 std::optional<std::uint64_t> predict(std::size_t
rank,
683 std::uint64_t next)
const {
684 const auto span = next - first;
689 first +
static_cast<std::uint64_t
>(
static_cast<unsigned __int128
>(
690 (
rank - 1) % SampleFrequency) *
691 span / SampleFrequency);
692 const std::size_t base = (guess / kBasicBits) * kBasicBits;
693 if (num_bits_ - base < kBasicBits) {
696 __builtin_prefetch(bits_.data() + base / 64, 0, 3);
697 const auto before = basic_prefix<Zero>(base);
698 const auto after = basic_prefix<Zero>(base + kBasicBits);
699 if (before <
rank &&
rank <= after) {
700 return select_basic<Zero>(base,
rank - before, after - before);
703 const auto lower = basic_prefix<Zero>(base - kBasicBits);
705 return select_basic<Zero>(base - kBasicBits,
rank - lower,
708 }
else if (
rank > after && num_bits_ - base >= 2 * kBasicBits) {
709 const auto upper = basic_prefix<Zero>(base + 2 * kBasicBits);
711 return select_basic<Zero>(base + kBasicBits,
rank - after,
719#if __has_cpp_attribute(gnu::noinline)
722 std::uint64_t select_small_index(std::size_t
rank,
723 std::uint64_t total)
const {
724 const std::uint64_t first_ones = local_rank(rank_record(0), 1);
725 const std::uint64_t first_end =
726 Zero ? std::min(kBasicBits, num_bits_) - first_ones : first_ones;
727 const std::size_t basic =
rank > first_end;
728 const std::uint64_t before = basic ? first_end : 0;
729 const std::uint64_t after = basic ? total : first_end;
730 return select_basic<Zero>(basic * kBasicBits,
rank - before,
735#if __has_cpp_attribute(gnu::always_inline)
736 [[gnu::always_inline]]
738 inline std::uint64_t select_basic(std::size_t base,
739 std::uint64_t remaining,
740 std::uint64_t population)
const {
741 if (population == kBasicBits) {
742 return base + remaining - 1;
744 if (num_bits_ - base >= kBasicBits) {
745 return base + detail::compressed::select_loaded_block<Zero>(
746 bits_.data() + base / 64, remaining - 1);
748 for (std::size_t lane = 0; lane < 8; ++lane) {
749 const std::size_t position = base + lane * 64;
750 if (position >= num_bits_) {
753 std::uint64_t word = bits_[position / 64];
754 if constexpr (Zero) {
758 ::first_bits_mask(std::min<std::size_t>(64, num_bits_ - position));
759 const unsigned count = std::popcount(word);
760 if (remaining <= count) {
761 return position + ::select_64(word, remaining - 1);
765 assert(
false &&
"Select localization must contain the target");