Pixie
Loading...
Searching...
No Matches
hybrid_btree.h
1#pragma once
2
3#include <pixie/bits.h>
4#include <pixie/memory_usage.h>
5#include <pixie/rmq.h>
6
7#include <algorithm>
8#include <array>
9#include <bit>
10#include <cstddef>
11#include <cstdint>
12#include <functional>
13#include <limits>
14#include <span>
15#include <stdexcept>
16#include <type_traits>
17#include <utility>
18#include <vector>
19
20namespace pixie::rmq {
21
26 PrefixSuffix,
27 BP,
28};
29
101template <class T,
102 class Compare = std::less<T>,
103 class Index = std::size_t,
104 std::size_t LeafSize = 496,
105 std::size_t Fanout = 256,
106 HybridBTreeLeafSelector LeafSelectorKind =
107 HybridBTreeLeafSelector::PrefixSuffix>
109 : public RmqBase<
110 HybridBTree<T, Compare, Index, LeafSize, Fanout, LeafSelectorKind>,
111 T> {
112 public:
113 static_assert(std::is_unsigned_v<Index>,
114 "HybridBTree index type must be unsigned");
115 static constexpr bool kBpLeafSelector =
116 LeafSelectorKind == HybridBTreeLeafSelector::BP;
117 static constexpr bool kMaskLeafSelector =
118 LeafSelectorKind == HybridBTreeLeafSelector::PrefixSuffix;
119 static_assert((kBpLeafSelector && LeafSize == 252) ||
120 (kMaskLeafSelector && (LeafSize == 248 || LeafSize == 496)),
121 "HybridBTree requires 252-value BP leaves "
122 "or 248/496-value prefix/suffix mask leaves");
123 static_assert(Fanout == 256,
124 "HybridBTree currently requires a 256 fanout template "
125 "argument");
126 static_assert(kBpLeafSelector || kMaskLeafSelector,
127 "unsupported HybridBTree low-level selector kind");
128
129 using Self =
131
132 static constexpr std::size_t npos = RmqBase<Self, T>::npos;
133 static constexpr Index invalid_index = std::numeric_limits<Index>::max();
134 static constexpr std::size_t kLeafSize = LeafSize;
135 static constexpr std::size_t kFanout = Fanout;
136 static constexpr std::size_t kMiddleFanout = 192;
137 static constexpr std::size_t kMinTopSparseBlockSize = 4096;
138 static constexpr std::size_t kMaxTopSparseBlocks = std::size_t{1} << 14;
139
143 HybridBTree() = default;
144
148 HybridBTree(const HybridBTree&) = default;
149
153 HybridBTree(HybridBTree&&) noexcept = default;
154
158 HybridBTree& operator=(const HybridBTree&) = default;
159
163 HybridBTree& operator=(HybridBTree&&) noexcept = default;
164
175 explicit HybridBTree(std::span<const T> values, Compare compare = Compare())
176 : values_(values), compare_(compare) {
177 build();
178 }
179
183 std::size_t size_impl() const { return values_.size(); }
184
188 T value_at_impl(std::size_t position) const { return values_[position]; }
189
197 std::size_t arg_min_impl(std::size_t left, std::size_t right) const {
198 if (left >= right || right > values_.size() || level_sizes_.empty()) {
199 return npos;
200 }
201
202 if (right - left <= top_block_size_) {
203 return tree_arg_min(left, right);
204 }
205 const std::size_t top_answer = top_sparse_arg_min(left, right);
206 return top_answer != npos ? top_answer : tree_arg_min(left, right);
207 }
208
212 static std::size_t top_sparse_block_size_for(std::size_t value_count) {
213 if (value_count == 0) {
214 return kMinTopSparseBlockSize;
215 }
216 return std::max(kMinTopSparseBlockSize,
217 1 + (value_count - 1) / kMaxTopSparseBlocks);
218 }
219
223 static std::size_t top_sparse_block_count_for(std::size_t value_count) {
224 if (value_count == 0) {
225 return 0;
226 }
227 const std::size_t block_size = top_sparse_block_size_for(value_count);
228 return 1 + (value_count - 1) / block_size;
229 }
230
234 std::size_t top_sparse_block_size() const { return top_block_size_; }
235
239 std::size_t top_sparse_block_count() const { return top_block_count_; }
240
247 std::size_t memory_usage_bytes_impl() const {
248 std::size_t bytes = sizeof(*this);
249 bytes += pixie::vector_capacity_bytes(leaf_selectors_);
250 bytes += pixie::vector_capacity_bytes(medium_selectors_);
251 bytes += pixie::vector_capacity_bytes(medium_min_values_);
252 bytes += pixie::vector_capacity_bytes(top_sparse_candidates_);
253 bytes += pixie::vector_capacity_bytes(medium_level_offsets_);
254 bytes += pixie::vector_capacity_bytes(level_sizes_);
255 bytes += pixie::vector_capacity_bytes(level_value_spans_);
256 bytes += pixie::vector_capacity_bytes(level_fanouts_);
257 return bytes;
258 }
259
260 private:
261 static constexpr std::size_t kSelectorEntries = 256;
262 static constexpr std::size_t kSelectorBits = 2 * kSelectorEntries;
263 static constexpr std::size_t kSelectorWords = kSelectorBits / 64;
264 static constexpr std::size_t kEmbeddedOffsetEntries = 252;
265 static constexpr std::size_t kEmbeddedPositionEntries = 192;
266 static constexpr std::size_t kEmbeddedOffsetBit = 2 * kEmbeddedOffsetEntries;
267 static constexpr std::size_t kEmbeddedPositionBit =
268 2 * kEmbeddedPositionEntries;
269 static constexpr std::size_t kMiddleZeroPrefixWord = 7;
270 static constexpr std::size_t kLeafLinearScanThreshold = 64;
271 static constexpr std::size_t kLeafAvx2ScanThreshold = 16;
272 static constexpr std::uint64_t kEmbeddedOffsetMask =
273 std::uint64_t{0xFF} << (kEmbeddedOffsetBit & 63);
274 static constexpr bool kInvalidIndexEqualsNpos =
275 static_cast<std::size_t>(invalid_index) == npos;
276
277 static_assert(kEmbeddedOffsetBit + 8 == kSelectorBits);
278 static_assert(kEmbeddedPositionBit + 128 == kSelectorBits);
279 static_assert(!kBpLeafSelector || LeafSize <= kEmbeddedOffsetEntries);
280 static_assert(kMiddleFanout <= kEmbeddedPositionEntries);
281
282 struct MinCandidate {
283 std::size_t position = npos;
284 const T* value = nullptr;
285 };
286
287 struct TopCandidate {
288 Index position = invalid_index;
289 };
290 static_assert(sizeof(TopCandidate) == sizeof(Index));
291
292 class alignas(64) Bp512Selector {
293 public:
297 Bp512Selector() = default;
298
307 template <class EntryLess>
308 void build(std::size_t entry_count, EntryLess entry_less) {
309 if (entry_count > kSelectorEntries) {
310 throw std::length_error("HybridBTree local selector too large");
311 }
312
313 bp_bits_.fill(0);
314 if (entry_count == 0) {
315 return;
316 }
317
318 std::array<std::uint16_t, kSelectorEntries> stack{};
319 std::size_t stack_size = 0;
320 std::size_t write_position = 2 * entry_count;
321
322 for (std::size_t i = entry_count; i-- > 0;) {
323 while (stack_size != 0 && !entry_less(stack[stack_size - 1], i)) {
324 --stack_size;
325 prepend_bp_bit(write_position, true);
326 }
327 stack[stack_size++] = static_cast<std::uint16_t>(i);
328 prepend_bp_bit(write_position, false);
329 }
330
331 while (write_position != 0) {
332 prepend_bp_bit(write_position, true);
333 }
334 }
335
342 std::size_t arg_min(std::size_t slot_left,
343 std::size_t slot_right,
344 std::size_t entry_count) const {
345 if (slot_left >= slot_right || slot_right > entry_count ||
346 entry_count > kSelectorEntries) {
347 return npos;
348 }
349 if (slot_left + 1 == slot_right) {
350 return slot_left;
351 }
352
353 const std::size_t bit_count = 2 * entry_count;
354 const std::size_t first_close = close_position(slot_left);
355 const std::size_t last_close = close_position(slot_right - 1);
356 if (first_close > last_close || last_close >= bit_count) {
357 return npos;
358 }
359
360 const std::size_t shifted_min =
361 depth_arg_min(first_close + 1, last_close + 2, bit_count);
362 if (shifted_min == npos || shifted_min == 0) {
363 return npos;
364 }
365
366 const std::size_t zero_rank = rank0_at(shifted_min, bit_count);
367 if (zero_rank == 0) {
368 return npos;
369 }
370 const std::size_t entry = zero_rank - 1;
371 return entry < entry_count ? entry : npos;
372 }
373
381 std::size_t arg_min_with_zero_prefix(std::size_t slot_left,
382 std::size_t slot_right,
383 std::size_t entry_count) const {
384 if (slot_left >= slot_right || slot_right > entry_count ||
385 entry_count > kEmbeddedPositionEntries) {
386 return npos;
387 }
388 if (slot_left + 1 == slot_right) {
389 return slot_left;
390 }
391
392 const std::size_t bit_count = 2 * entry_count;
393 const std::size_t first_close =
394 close_position_with_zero_prefix(slot_left, bit_count);
395 const std::size_t last_close =
396 close_position_with_zero_prefix(slot_right - 1, bit_count);
397 if (first_close > last_close || last_close >= bit_count) {
398 return npos;
399 }
400
401 const std::size_t shifted_min = depth_arg_min_with_zero_prefix(
402 first_close + 1, last_close + 2, bit_count);
403 if (shifted_min == npos || shifted_min == 0) {
404 return npos;
405 }
406
407 const std::size_t zero_rank =
408 rank0_at_with_zero_prefix(shifted_min, bit_count);
409 if (zero_rank == 0) {
410 return npos;
411 }
412 const std::size_t entry = zero_rank - 1;
413 return entry < entry_count ? entry : npos;
414 }
415
419 std::size_t close_position(std::size_t slot) const {
420 return select0_512(bp_bits_.data(), slot);
421 }
422
426 std::size_t rank0_at(std::size_t position, std::size_t bit_count) const {
427 position = std::min(position, bit_count);
428 return position - rank_512(bp_bits_.data(), position);
429 }
430
434 void set_embedded_min_offset(std::size_t offset) {
435 bp_bits_[kEmbeddedOffsetBit >> 6] =
436 (bp_bits_[kEmbeddedOffsetBit >> 6] & ~kEmbeddedOffsetMask) |
437 ((static_cast<std::uint64_t>(offset) & 0xFFu)
438 << (kEmbeddedOffsetBit & 63));
439 }
440
444 std::uint8_t embedded_min_offset() const {
445 return static_cast<std::uint8_t>(
446 (bp_bits_[kEmbeddedOffsetBit >> 6] & kEmbeddedOffsetMask) >>
447 (kEmbeddedOffsetBit & 63));
448 }
449
453 void set_embedded_min_position(std::size_t position) {
454 bp_bits_[kEmbeddedPositionBit >> 6] =
455 static_cast<std::uint64_t>(position);
456 }
457
461 std::size_t embedded_min_position() const {
462 return static_cast<std::size_t>(bp_bits_[kEmbeddedPositionBit >> 6]);
463 }
464
468 void build_zero_prefix_metadata(std::size_t bit_count) {
469 const std::size_t word_count = (bit_count + 63) / 64;
470 std::uint64_t packed = 0;
471 std::size_t zeros = 0;
472 for (std::size_t word = 0; word <= word_count; ++word) {
473 packed |= (static_cast<std::uint64_t>(zeros) & 0xFFu) << (8 * word);
474 if (word == word_count) {
475 break;
476 }
477 const std::size_t word_begin = word * 64;
478 const std::size_t word_bits =
479 std::min<std::size_t>(64, bit_count - word_begin);
480 const std::uint64_t bits = bp_bits_[word] & first_bits_mask(word_bits);
481 zeros += word_bits - std::popcount(bits);
482 }
483 bp_bits_[kMiddleZeroPrefixWord] = packed;
484 }
485
486 private:
490 std::size_t prepend_bp_bit(std::size_t& write_position, bool bit) {
491 --write_position;
492 if (bit) {
493 bp_bits_[write_position >> 6] |= std::uint64_t{1}
494 << (write_position & 63);
495 }
496 return write_position;
497 }
498
502 int prefix_excess(std::size_t position) const {
503 position = std::min(position, kSelectorBits);
504 const std::size_t ones = rank_512(bp_bits_.data(), position);
505 return static_cast<int>(2 * ones) - static_cast<int>(position);
506 }
507
511 std::size_t zero_prefix_at_word(std::size_t word) const {
512 return static_cast<std::uint8_t>(bp_bits_[kMiddleZeroPrefixWord] >>
513 (8 * word));
514 }
515
519 std::size_t rank0_at_with_zero_prefix(std::size_t position,
520 std::size_t bit_count) const {
521 position = std::min(position, bit_count);
522 const std::size_t full_words = position >> 6;
523 std::size_t zeros = zero_prefix_at_word(full_words);
524 const std::size_t tail_bits = position & 63;
525 if (tail_bits != 0) {
526 const std::uint64_t tail =
527 bp_bits_[full_words] & first_bits_mask(tail_bits);
528 zeros += tail_bits - std::popcount(tail);
529 }
530 return zeros;
531 }
532
536 int prefix_excess_with_zero_prefix(std::size_t position,
537 std::size_t bit_count) const {
538 position = std::min(position, bit_count);
539 return static_cast<int>(position) -
540 2 * static_cast<int>(
541 rank0_at_with_zero_prefix(position, bit_count));
542 }
543
547 std::size_t close_position_with_zero_prefix(std::size_t slot,
548 std::size_t bit_count) const {
549 const std::size_t word_count = (bit_count + 63) / 64;
550 for (std::size_t word = 0; word < word_count; ++word) {
551 const std::size_t next_zero_prefix = zero_prefix_at_word(word + 1);
552 if (next_zero_prefix <= slot) {
553 continue;
554 }
555 const std::size_t local_rank = slot - zero_prefix_at_word(word);
556 const std::size_t word_begin = word * 64;
557 const std::size_t word_bits =
558 std::min<std::size_t>(64, bit_count - word_begin);
559 const std::uint64_t zeros =
560 (~bp_bits_[word]) & first_bits_mask(word_bits);
561 return word_begin + select_64(zeros, local_rank);
562 }
563 return npos;
564 }
565
569 std::size_t depth_arg_min(std::size_t left,
570 std::size_t right,
571 std::size_t bit_count) const {
572 const std::size_t depth_count = bit_count + 1;
573 if (left >= right || right > depth_count) {
574 return npos;
575 }
576
577 std::size_t position = left;
578 int best_depth = prefix_excess(position);
579 std::size_t best_position = position;
580
581 while (position < right) {
582 const std::size_t chunk_begin = (position / 128) * 128;
583 const std::size_t local_left = position - chunk_begin;
584 const std::size_t local_right =
585 std::min<std::size_t>(right - 1, chunk_begin + 128) - chunk_begin;
586
587 int candidate_depth;
588 std::size_t candidate_position;
589 if (chunk_begin >= bit_count) {
590 candidate_depth = prefix_excess(chunk_begin);
591 candidate_position = chunk_begin;
592 } else {
593 const std::size_t word = chunk_begin >> 6;
594 const ExcessResult result =
595 excess_min_128(bp_bits_.data() + word, local_left, local_right);
596 candidate_depth = prefix_excess(chunk_begin) + result.min_excess;
597 candidate_position = chunk_begin + result.offset;
598 }
599
600 if (candidate_depth < best_depth) {
601 best_depth = candidate_depth;
602 best_position = candidate_position;
603 }
604
605 position = chunk_begin + local_right + 1;
606 }
607
608 return best_position;
609 }
610
614 std::size_t depth_arg_min_with_zero_prefix(std::size_t left,
615 std::size_t right,
616 std::size_t bit_count) const {
617 const std::size_t depth_count = bit_count + 1;
618 if (left >= right || right > depth_count) {
619 return npos;
620 }
621
622 std::size_t position = left;
623 int best_depth = prefix_excess_with_zero_prefix(position, bit_count);
624 std::size_t best_position = position;
625
626 while (position < right) {
627 const std::size_t chunk_begin = (position / 128) * 128;
628 const std::size_t local_left = position - chunk_begin;
629 const std::size_t local_right =
630 std::min<std::size_t>(right - 1, chunk_begin + 128) - chunk_begin;
631
632 int candidate_depth;
633 std::size_t candidate_position;
634 if (chunk_begin >= bit_count) {
635 candidate_depth =
636 prefix_excess_with_zero_prefix(chunk_begin, bit_count);
637 candidate_position = chunk_begin;
638 } else {
639 const std::size_t word = chunk_begin >> 6;
640 const ExcessResult result =
641 excess_min_128(bp_bits_.data() + word, local_left, local_right);
642 candidate_depth =
643 prefix_excess_with_zero_prefix(chunk_begin, bit_count) +
644 result.min_excess;
645 candidate_position = chunk_begin + result.offset;
646 }
647
648 if (candidate_depth < best_depth) {
649 best_depth = candidate_depth;
650 best_position = candidate_position;
651 }
652
653 position = chunk_begin + local_right + 1;
654 }
655
656 return best_position;
657 }
658
659 std::array<std::uint64_t, kSelectorWords> bp_bits_{};
660 };
661
662 static_assert(sizeof(Bp512Selector) == 64);
663
664 class alignas(LeafSize == 496 ? 64 : 32) PrefixSuffixMaskLeafSelector {
665 public:
669 PrefixSuffixMaskLeafSelector() = default;
670
678 template <class EntryLess>
679 void build(std::size_t entry_count, EntryLess entry_less) {
680 if (entry_count > kMaskEntries) {
681 throw std::length_error(
682 "HybridBTree prefix/suffix leaf selector too large");
683 }
684
685 words_.fill(0);
686 if (entry_count == 0) {
687 set_embedded_min_offset(0);
688 return;
689 }
690
691 std::size_t prefix_best = 0;
692 set_mask_bit(0);
693 for (std::size_t slot = 1; slot < entry_count; ++slot) {
694 if (entry_less(slot, prefix_best)) {
695 prefix_best = slot;
696 set_mask_bit(slot);
697 }
698 }
699
700 std::size_t suffix_best = entry_count - 1;
701 set_mask_bit(suffix_best);
702 for (std::size_t slot = entry_count - 1; slot > 0;) {
703 --slot;
704 if (!entry_less(suffix_best, slot)) {
705 suffix_best = slot;
706 set_mask_bit(slot);
707 }
708 }
709
710 set_embedded_min_offset(prefix_best);
711 }
712
721 std::size_t arg_min(std::size_t slot_left,
722 std::size_t slot_right,
723 std::size_t entry_count) const {
724 if (slot_left >= slot_right || slot_right > entry_count ||
725 entry_count > kMaskEntries) {
726 return npos;
727 }
728 if (slot_left + 1 == slot_right) {
729 return slot_left;
730 }
731
732 const std::size_t min_offset = embedded_min_offset();
733 if (slot_left <= min_offset && min_offset < slot_right) {
734 return min_offset;
735 }
736 if (slot_left == 0 && slot_right <= min_offset) {
737 return previous_set_bit_before(slot_right);
738 }
739 if (slot_right == entry_count && min_offset < slot_left) {
740 return next_set_bit_at_or_after(slot_left);
741 }
742 return npos;
743 }
744
748 void set_embedded_min_offset(std::size_t offset) {
749 words_[kOffsetWord] =
750 (words_[kOffsetWord] & kMaskWordMask) |
751 ((static_cast<std::uint64_t>(offset) & kOffsetMask) << kOffsetShift);
752 }
753
757 std::size_t embedded_min_offset() const {
758 return static_cast<std::size_t>((words_[kOffsetWord] >> kOffsetShift) &
759 kOffsetMask);
760 }
761
762 private:
763 static constexpr std::size_t kMaskEntries = LeafSize;
764 static constexpr std::size_t kOffsetBits = LeafSize == 496 ? 16 : 8;
765 static constexpr std::size_t kPackedBits = kMaskEntries + kOffsetBits;
766 static constexpr std::size_t kWordCount = (kPackedBits + 63) / 64;
767 static constexpr std::size_t kOffsetWord = kMaskEntries / 64;
768 static constexpr std::size_t kOffsetShift = kMaskEntries & 63;
769
773 static constexpr std::uint64_t low_bits_mask(std::size_t bits) {
774 if (bits == 0) {
775 return 0;
776 }
777 if (bits >= 64) {
778 return std::numeric_limits<std::uint64_t>::max();
779 }
780 return (std::uint64_t{1} << bits) - 1;
781 }
782
783 static constexpr std::uint64_t kOffsetMask = low_bits_mask(kOffsetBits);
784 static constexpr std::uint64_t kMaskWordMask = low_bits_mask(kOffsetShift);
785
786 static_assert(!kMaskLeafSelector || kPackedBits % 64 == 0);
787 static_assert(!kMaskLeafSelector || kOffsetShift + kOffsetBits == 64);
788
792 void set_mask_bit(std::size_t slot) {
793 words_[slot >> 6] |= std::uint64_t{1} << (slot & 63);
794 }
795
799 std::uint64_t mask_word(std::size_t word) const {
800 return word == kOffsetWord ? words_[word] & kMaskWordMask : words_[word];
801 }
802
806 std::size_t previous_set_bit_before(std::size_t limit) const {
807 if (limit == 0) {
808 return npos;
809 }
810
811 std::size_t word = (limit - 1) >> 6;
812 std::uint64_t bits =
813 mask_word(word) & first_bits_mask(((limit - 1) & 63) + 1);
814 while (true) {
815 if (bits != 0) {
816 return word * 64 + 63 - std::countl_zero(bits);
817 }
818 if (word == 0) {
819 break;
820 }
821 --word;
822 bits = mask_word(word);
823 }
824 return npos;
825 }
826
830 std::size_t next_set_bit_at_or_after(std::size_t slot) const {
831 std::size_t word = slot >> 6;
832 std::uint64_t bits = mask_word(word) & ~first_bits_mask(slot & 63);
833 while (word < kWordCount) {
834 if (bits != 0) {
835 const std::size_t result = word * 64 + std::countr_zero(bits);
836 return result < kMaskEntries ? result : npos;
837 }
838 ++word;
839 bits = word < kWordCount ? mask_word(word) : 0;
840 }
841 return npos;
842 }
843
844 std::array<std::uint64_t, kWordCount> words_{};
845 };
846
847 static_assert(!kMaskLeafSelector || sizeof(PrefixSuffixMaskLeafSelector) ==
848 (LeafSize == 496 ? 64 : 32));
849 static_assert(!kMaskLeafSelector || alignof(PrefixSuffixMaskLeafSelector) ==
850 (LeafSize == 496 ? 64 : 32));
851
852 using LeafSelector = std::conditional_t<kMaskLeafSelector,
853 PrefixSuffixMaskLeafSelector,
854 Bp512Selector>;
855
859 bool missing_position(std::size_t position) const {
860 if constexpr (kInvalidIndexEqualsNpos) {
861 return position == npos;
862 } else {
863 return position == npos ||
864 position == static_cast<std::size_t>(invalid_index);
865 }
866 }
867
871 MinCandidate value_candidate(std::size_t position) const {
872 if (missing_position(position) || position >= values_.size()) {
873 return {};
874 }
875 return {position, values_.data() + position};
876 }
877
881 MinCandidate subtree_min_candidate(std::size_t level,
882 std::size_t node) const {
883 const std::size_t position = subtree_min_position(level, node);
884 if (missing_position(position)) {
885 return {};
886 }
887 return {position, &subtree_min_value(level, node)};
888 }
889
893 MinCandidate subtree_child_min_candidate(std::size_t level,
894 std::size_t node,
895 std::size_t slot) const {
896 const std::size_t child_level = level - 1;
897 return subtree_min_candidate(child_level,
898 node * fanout_at_level(level) + slot);
899 }
900
904 bool strictly_better_candidate(MinCandidate left, MinCandidate right) const {
905 if (missing_position(left.position)) {
906 return false;
907 }
908 if (missing_position(right.position)) {
909 return true;
910 }
911 return compare_(*left.value, *right.value);
912 }
913
917 MinCandidate better_candidate(MinCandidate left, MinCandidate right) const {
918 if (missing_position(left.position)) {
919 return right;
920 }
921 if (missing_position(right.position)) {
922 return left;
923 }
924 if (compare_(*right.value, *left.value)) {
925 return right;
926 }
927 if (compare_(*left.value, *right.value)) {
928 return left;
929 }
930 return right.position < left.position ? right : left;
931 }
932
936 bool strictly_better_subtree_child_slot(std::size_t level,
937 std::size_t node,
938 std::size_t left_slot,
939 std::size_t right_slot) const {
940 return strictly_better_candidate(
941 subtree_child_min_candidate(level, node, left_slot),
942 subtree_child_min_candidate(level, node, right_slot));
943 }
944
948 void build() {
949 leaf_selectors_.clear();
950 medium_selectors_.clear();
951 medium_min_values_.clear();
952 top_sparse_candidates_.clear();
953 top_block_size_ = kMinTopSparseBlockSize;
954 top_block_count_ = 0;
955 top_sparse_levels_ = 0;
956 medium_level_offsets_.clear();
957 level_sizes_.clear();
958 level_value_spans_.clear();
959 level_fanouts_.clear();
960 if (values_.empty()) {
961 return;
962 }
963 if (values_.size() > static_cast<std::size_t>(invalid_index)) {
964 throw std::length_error("HybridBTree index type is too small");
965 }
966
967 initialize_layout((values_.size() + LeafSize - 1) / LeafSize);
968 for (std::size_t leaf = 0; leaf < level_sizes_[0]; ++leaf) {
969 build_leaf(leaf);
970 }
971
972 for (std::size_t level = 1; level < level_count(); ++level) {
973 for (std::size_t node = 0; node < level_sizes_[level]; ++node) {
974 build_internal_node(level, node);
975 }
976 }
977 build_top_sparse_table();
978 }
979
986 void initialize_layout(std::size_t leaf_count) {
987 level_sizes_.push_back(leaf_count);
988 level_value_spans_.push_back(LeafSize);
989 level_fanouts_.push_back(0);
990
991 std::size_t current_count = leaf_count;
992 std::size_t current_span = LeafSize;
993 while (current_count > 1) {
994 level_fanouts_.push_back(kMiddleFanout);
995 current_count = ceil_div(current_count, kMiddleFanout);
996 current_span = saturating_product(current_span, kMiddleFanout);
997 level_sizes_.push_back(current_count);
998 level_value_spans_.push_back(current_span);
999 }
1000
1001 leaf_selectors_.resize(level_sizes_[0]);
1002
1003 medium_level_offsets_.assign(level_count(), 0);
1004 if (level_count() <= 1) {
1005 return;
1006 }
1007
1008 std::size_t medium_node_count = 0;
1009 for (std::size_t level = 1; level < level_count(); ++level) {
1010 medium_level_offsets_[level] = medium_node_count;
1011 medium_node_count += level_sizes_[level];
1012 }
1013 medium_selectors_.resize(medium_node_count);
1014 medium_min_values_.reserve(medium_node_count);
1015 }
1016
1020 void build_leaf(std::size_t leaf) {
1021 LeafSelector& selector = leaf_selectors_[leaf];
1022 const std::size_t begin = node_value_begin(0, leaf);
1023 const std::size_t count = entry_count(0, leaf);
1024 selector.build(count, [&](std::size_t left, std::size_t right) {
1025 return compare_(values_[begin + left], values_[begin + right]);
1026 });
1027
1028 if constexpr (kBpLeafSelector) {
1029 const std::size_t slot = selector.arg_min(0, count, count);
1030 selector.set_embedded_min_offset(slot);
1031 }
1032 }
1033
1037 void build_internal_node(std::size_t level, std::size_t node) {
1038 Bp512Selector& selector = mutable_selector_at(level, node);
1039 const std::size_t count = entry_count(level, node);
1040 const std::size_t first_child = node * fanout_at_level(level);
1041
1042 selector.build(count, [&](std::size_t left, std::size_t right) {
1043 return strictly_better_subtree_child_slot(level, node, left, right);
1044 });
1045
1046 const std::size_t slot = selector.arg_min(0, count, count);
1047 const std::size_t min_position =
1048 subtree_min_position(level - 1, first_child + slot);
1049 selector.set_embedded_min_position(min_position);
1050 medium_min_values_.push_back(values_[min_position]);
1051 selector.build_zero_prefix_metadata(2 * count);
1052 }
1053
1057 static std::size_t saturating_product(std::size_t left, std::size_t right) {
1058 if (left != 0 && right > std::numeric_limits<std::size_t>::max() / left) {
1059 return std::numeric_limits<std::size_t>::max();
1060 }
1061 return left * right;
1062 }
1063
1067 static std::size_t ceil_div(std::size_t value, std::size_t divisor) {
1068 return value == 0 ? 0 : 1 + (value - 1) / divisor;
1069 }
1070
1074 std::size_t tree_arg_min(std::size_t left, std::size_t right) const {
1075 const std::size_t root_level = level_count() - 1;
1076 if (left == 0 && right == values_.size()) {
1077 return subtree_min_position(root_level, 0);
1078 }
1079
1080 const std::size_t left_leaf = leaf_for_value(left);
1081 const std::size_t right_leaf = leaf_for_value(right - 1);
1082 if (left_leaf == right_leaf) {
1083 return leaf_range_min(left_leaf, left, right);
1084 }
1085
1086 const auto [level, node_index] = covering_node(left_leaf, right_leaf);
1087 return query_node(level, node_index, left, right).position;
1088 }
1089
1097 std::size_t leaf_range_min(std::size_t leaf,
1098 std::size_t left,
1099 std::size_t right) const {
1100 if (left >= right) {
1101 return npos;
1102 }
1103
1104 const std::size_t begin = node_value_begin(0, leaf);
1105 const std::size_t end = node_value_end(0, leaf);
1106 if (left <= begin && end <= right) {
1107 return subtree_min_position(0, leaf);
1108 }
1109 const std::size_t slot_left = left - begin;
1110 const std::size_t slot_right = right - begin;
1111
1112 if constexpr (kMaskLeafSelector) {
1113 const std::size_t slot = leaf_selectors_[leaf].arg_min(
1114 slot_left, slot_right, entry_count(0, leaf));
1115 if (slot != npos) {
1116 return begin + slot;
1117 }
1118 return linear_range_min(left, right);
1119 } else {
1120 if (right - left <= kLeafLinearScanThreshold) {
1121 return linear_range_min(left, right);
1122 }
1123
1124 const std::size_t slot = leaf_selectors_[leaf].arg_min(
1125 slot_left, slot_right, entry_count(0, leaf));
1126 if (slot == npos) {
1127 return npos;
1128 }
1129 return begin + slot;
1130 }
1131 }
1132
1136 std::size_t linear_range_min(std::size_t left, std::size_t right) const {
1137 if (left >= right) {
1138 return npos;
1139 }
1140#ifdef PIXIE_AVX2_SUPPORT
1141 if constexpr (std::is_same_v<T, std::int64_t> &&
1142 std::is_same_v<Compare, std::less<T>>) {
1143 if (right - left >= kLeafAvx2ScanThreshold) {
1144 return linear_range_min_i64_avx2(left, right);
1145 }
1146 }
1147#endif
1148 std::size_t best = left;
1149 for (std::size_t position = left + 1; position < right; ++position) {
1150 if (compare_(values_[position], values_[best])) {
1151 best = position;
1152 }
1153 }
1154 return best;
1155 }
1156
1157#ifdef PIXIE_AVX2_SUPPORT
1165 std::size_t linear_range_min_i64_avx2(std::size_t left,
1166 std::size_t right) const {
1167 const std::int64_t* data = values_.data();
1168 std::size_t position = left;
1169
1170 __m256i best_values =
1171 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(data + position));
1172 __m256i best_positions = _mm256_set_epi64x(
1173 static_cast<long long>(position + 3),
1174 static_cast<long long>(position + 2),
1175 static_cast<long long>(position + 1), static_cast<long long>(position));
1176 position += 4;
1177
1178 for (; position + 4 <= right; position += 4) {
1179 const __m256i values =
1180 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(data + position));
1181 const __m256i positions =
1182 _mm256_set_epi64x(static_cast<long long>(position + 3),
1183 static_cast<long long>(position + 2),
1184 static_cast<long long>(position + 1),
1185 static_cast<long long>(position));
1186 const __m256i take_new = _mm256_cmpgt_epi64(best_values, values);
1187 best_values = _mm256_blendv_epi8(best_values, values, take_new);
1188 best_positions = _mm256_blendv_epi8(best_positions, positions, take_new);
1189 }
1190
1191 alignas(32) std::int64_t value_lanes[4];
1192 alignas(32) std::uint64_t position_lanes[4];
1193 _mm256_store_si256(reinterpret_cast<__m256i*>(value_lanes), best_values);
1194 _mm256_store_si256(reinterpret_cast<__m256i*>(position_lanes),
1195 best_positions);
1196
1197 std::int64_t best_value = value_lanes[0];
1198 std::size_t best_position = static_cast<std::size_t>(position_lanes[0]);
1199 for (std::size_t lane = 1; lane < 4; ++lane) {
1200 const std::size_t lane_position =
1201 static_cast<std::size_t>(position_lanes[lane]);
1202 if (value_lanes[lane] < best_value ||
1203 (value_lanes[lane] == best_value && lane_position < best_position)) {
1204 best_value = value_lanes[lane];
1205 best_position = lane_position;
1206 }
1207 }
1208
1209 for (; position < right; ++position) {
1210 if (data[position] < best_value) {
1211 best_value = data[position];
1212 best_position = position;
1213 }
1214 }
1215 return best_position;
1216 }
1217#endif
1218
1222 std::pair<std::size_t, std::size_t> covering_node(
1223 std::size_t left_leaf,
1224 std::size_t right_leaf) const {
1225 std::size_t level = 0;
1226 std::size_t left_node = left_leaf;
1227 std::size_t right_node = right_leaf;
1228 while (left_node != right_node) {
1229 ++level;
1230 const std::size_t fanout = fanout_at_level(level);
1231 left_node /= fanout;
1232 right_node /= fanout;
1233 }
1234 return {level, left_node};
1235 }
1236
1240 std::size_t leaf_for_value(std::size_t position) const {
1241 return position / LeafSize;
1242 }
1243
1247 std::size_t child_for_value(std::size_t child_level,
1248 std::size_t position) const {
1249 return position / level_value_spans_[child_level];
1250 }
1251
1260 MinCandidate query_child_slots(std::size_t level,
1261 std::size_t node,
1262 std::size_t slot_left,
1263 std::size_t slot_right,
1264 std::size_t left,
1265 std::size_t right) const {
1266 if (slot_left >= slot_right) {
1267 return {};
1268 }
1269
1270 const std::size_t count = entry_count(level, node);
1271 const std::size_t slot =
1272 slot_left + 1 == slot_right
1273 ? slot_left
1274 : selector_arg_min(level, node, slot_left, slot_right, count);
1275 if (slot == npos) {
1276 return {};
1277 }
1278
1279 const std::size_t child_level = level - 1;
1280 const std::size_t first_child = node * fanout_at_level(level);
1281 const std::size_t child = first_child + slot;
1282 const MinCandidate child_min = subtree_min_candidate(child_level, child);
1283 const std::size_t child_begin = node_value_begin(child_level, child);
1284 const std::size_t child_end = node_value_end(child_level, child);
1285 if ((left <= child_begin && child_end <= right) ||
1286 contains_position(left, right, child_min.position)) {
1287 return child_min;
1288 }
1289
1290 const std::size_t last_slot = slot_right - 1;
1291 const std::size_t left_child_begin =
1292 node_value_begin(child_level, first_child + slot_left);
1293 const std::size_t left_child_end =
1294 node_value_end(child_level, first_child + slot_left);
1295 MinCandidate answer = query_node(child_level, first_child + slot_left,
1296 std::max(left, left_child_begin),
1297 std::min(right, left_child_end));
1298
1299 if (slot_left != last_slot) {
1300 const std::size_t right_child_begin =
1301 node_value_begin(child_level, first_child + last_slot);
1302 const std::size_t right_child_end =
1303 node_value_end(child_level, first_child + last_slot);
1304 answer = better_candidate(answer,
1305 query_node(child_level, first_child + last_slot,
1306 std::max(left, right_child_begin),
1307 std::min(right, right_child_end)));
1308 }
1309
1310 if (slot_left + 1 < last_slot) {
1311 answer = better_candidate(
1312 answer,
1313 full_child_slot_range_min(level, node, slot_left + 1, last_slot));
1314 }
1315
1316 return answer;
1317 }
1318
1322 MinCandidate full_child_slot_range_min(std::size_t level,
1323 std::size_t node,
1324 std::size_t slot_left,
1325 std::size_t slot_right) const {
1326 if (slot_left >= slot_right) {
1327 return {};
1328 }
1329
1330 const std::size_t slot =
1331 slot_left + 1 == slot_right
1332 ? slot_left
1333 : selector_arg_min(level, node, slot_left, slot_right,
1334 entry_count(level, node));
1335 if (slot == npos) {
1336 return {};
1337 }
1338
1339 return subtree_child_min_candidate(level, node, slot);
1340 }
1341
1345 MinCandidate query_node(std::size_t level,
1346 std::size_t node,
1347 std::size_t left,
1348 std::size_t right) const {
1349 if (left >= right) {
1350 return {};
1351 }
1352 const std::size_t begin = node_value_begin(level, node);
1353 const std::size_t end = node_value_end(level, node);
1354 if (left <= begin && end <= right) {
1355 return subtree_min_candidate(level, node);
1356 }
1357 if (level == 0) {
1358 return value_candidate(leaf_range_min(node, left, right));
1359 }
1360
1361 const std::size_t child_level = level - 1;
1362 const std::size_t left_child = child_for_value(child_level, left);
1363 const std::size_t right_child = child_for_value(child_level, right - 1);
1364 const std::size_t first_child = node * fanout_at_level(level);
1365 const std::size_t left_slot = left_child - first_child;
1366 const std::size_t right_slot = right_child - first_child + 1;
1367 return query_child_slots(level, node, left_slot, right_slot, left, right);
1368 }
1369
1373 bool contains_position(std::size_t left,
1374 std::size_t right,
1375 std::size_t position) const {
1376 return !missing_position(position) && left <= position && position < right;
1377 }
1378
1382 std::size_t level_count() const { return level_sizes_.size(); }
1383
1387 std::size_t entry_count(std::size_t level, std::size_t node) const {
1388 if (level == 0) {
1389 const std::size_t begin = node_value_begin(0, node);
1390 return std::min<std::size_t>(LeafSize, values_.size() - begin);
1391 }
1392 const std::size_t first_child = node * fanout_at_level(level);
1393 return std::min<std::size_t>(fanout_at_level(level),
1394 level_sizes_[level - 1] - first_child);
1395 }
1396
1400 std::size_t node_value_begin(std::size_t level, std::size_t node) const {
1401 return node * level_value_spans_[level];
1402 }
1403
1407 std::size_t node_value_end(std::size_t level, std::size_t node) const {
1408 return std::min(values_.size(),
1409 node_value_begin(level, node) + level_value_spans_[level]);
1410 }
1411
1415 std::size_t subtree_min_position(std::size_t level, std::size_t node) const {
1416 if (level == 0) {
1417 return node_value_begin(0, node) +
1418 leaf_selectors_[node].embedded_min_offset();
1419 }
1420 return selector_at(level, node).embedded_min_position();
1421 }
1422
1426 const T& subtree_min_value(std::size_t level, std::size_t node) const {
1427 if (level == 0) {
1428 return values_[subtree_min_position(0, node)];
1429 }
1430 return medium_min_values_[medium_flat_index(level, node)];
1431 }
1432
1436 const Bp512Selector& selector_at(std::size_t level, std::size_t node) const {
1437 return medium_selectors_[medium_flat_index(level, node)];
1438 }
1439
1443 Bp512Selector& mutable_selector_at(std::size_t level, std::size_t node) {
1444 return medium_selectors_[medium_flat_index(level, node)];
1445 }
1446
1450 std::size_t selector_arg_min(std::size_t level,
1451 std::size_t node,
1452 std::size_t slot_left,
1453 std::size_t slot_right,
1454 std::size_t count) const {
1455 const Bp512Selector& selector = selector_at(level, node);
1456 return selector.arg_min_with_zero_prefix(slot_left, slot_right, count);
1457 }
1458
1462 std::size_t medium_flat_index(std::size_t level, std::size_t node) const {
1463 return medium_level_offsets_[level] + node;
1464 }
1465
1469 std::size_t fanout_at_level(std::size_t level) const {
1470 return level_fanouts_[level];
1471 }
1472
1476 void build_top_sparse_table() {
1477 top_sparse_candidates_.clear();
1478 top_block_size_ = top_sparse_block_size_for(values_.size());
1479 top_block_count_ = top_sparse_block_count_for(values_.size());
1480 top_sparse_levels_ =
1481 top_block_count_ == 0 ? 0 : std::bit_width(top_block_count_);
1482 if (top_block_count_ == 0) {
1483 return;
1484 }
1485
1486 top_sparse_candidates_.assign(top_sparse_levels_ * top_block_count_,
1487 TopCandidate{});
1488 for (std::size_t block = 0; block < top_block_count_; ++block) {
1489 const std::size_t begin = block * top_block_size_;
1490 const std::size_t end = std::min(values_.size(), begin + top_block_size_);
1491 std::size_t minimum = begin;
1492 for (std::size_t position = begin + 1; position < end; ++position) {
1493 if (strictly_better_value_position(position, minimum)) {
1494 minimum = position;
1495 }
1496 }
1497 top_sparse_candidates_[block] = make_top_candidate(minimum);
1498 }
1499
1500 for (std::size_t level = 1; level < top_sparse_levels_; ++level) {
1501 const std::size_t span = std::size_t{1} << level;
1502 const std::size_t half_span = span >> 1;
1503 TopCandidate* current =
1504 top_sparse_candidates_.data() + level * top_block_count_;
1505 const TopCandidate* previous =
1506 top_sparse_candidates_.data() + (level - 1) * top_block_count_;
1507 for (std::size_t block = 0; block + span <= top_block_count_; ++block) {
1508 current[block] =
1509 better_top_candidate(previous[block], previous[block + half_span]);
1510 }
1511 }
1512 }
1513
1517 TopCandidate make_top_candidate(std::size_t position) const {
1518 if (!valid_value_position(position)) {
1519 return {};
1520 }
1521 return {static_cast<Index>(position)};
1522 }
1523
1527 bool valid_value_position(std::size_t position) const {
1528 return !missing_position(position) && position < values_.size();
1529 }
1530
1535 bool strictly_better_value_position(std::size_t left,
1536 std::size_t right) const {
1537 if (!valid_value_position(left)) {
1538 return false;
1539 }
1540 if (!valid_value_position(right)) {
1541 return true;
1542 }
1543 if (compare_(values_[left], values_[right])) {
1544 return true;
1545 }
1546 if (compare_(values_[right], values_[left])) {
1547 return false;
1548 }
1549 return left < right;
1550 }
1551
1555 TopCandidate better_top_candidate(TopCandidate left,
1556 TopCandidate right) const {
1557 const std::size_t left_position = static_cast<std::size_t>(left.position);
1558 const std::size_t right_position = static_cast<std::size_t>(right.position);
1559 return strictly_better_value_position(right_position, left_position) ? right
1560 : left;
1561 }
1562
1566 TopCandidate top_sparse_block_arg_min(std::size_t block_left,
1567 std::size_t block_right) const {
1568 if (block_left >= block_right || block_right > top_block_count_ ||
1569 top_sparse_levels_ == 0) {
1570 return {};
1571 }
1572 const std::size_t length = block_right - block_left;
1573 const std::size_t level = std::bit_width(length) - 1;
1574 const std::size_t span = std::size_t{1} << level;
1575 const TopCandidate* table =
1576 top_sparse_candidates_.data() + level * top_block_count_;
1577 return better_top_candidate(table[block_left], table[block_right - span]);
1578 }
1579
1583 bool top_candidate_inside(TopCandidate candidate,
1584 std::size_t left,
1585 std::size_t right) const {
1586 const std::size_t position = static_cast<std::size_t>(candidate.position);
1587 return valid_value_position(position) && left <= position &&
1588 position < right;
1589 }
1590
1594 std::size_t top_sparse_arg_min(std::size_t left, std::size_t right) const {
1595 if (top_block_count_ <= 1) {
1596 return npos;
1597 }
1598
1599 const std::size_t padded_block_left = left / top_block_size_;
1600 const std::size_t padded_block_right = (right - 1) / top_block_size_ + 1;
1601 if (padded_block_left + 1 >= padded_block_right) {
1602 return npos;
1603 }
1604
1605 const TopCandidate padded =
1606 top_sparse_block_arg_min(padded_block_left, padded_block_right);
1607 if (top_candidate_inside(padded, left, right)) {
1608 return static_cast<std::size_t>(padded.position);
1609 }
1610
1611 const std::size_t first_full_block =
1612 (left + top_block_size_ - 1) / top_block_size_;
1613 const std::size_t full_block_right = right / top_block_size_;
1614 if (first_full_block >= full_block_right) {
1615 return npos;
1616 }
1617
1618 TopCandidate answer =
1619 top_sparse_block_arg_min(first_full_block, full_block_right);
1620
1621 const std::size_t left_border_end = first_full_block * top_block_size_;
1622 if (left < left_border_end) {
1623 answer = better_top_candidate(
1624 answer, make_top_candidate(tree_arg_min(left, left_border_end)));
1625 }
1626
1627 const std::size_t right_border_begin = full_block_right * top_block_size_;
1628 if (right_border_begin < right) {
1629 answer = better_top_candidate(
1630 answer, make_top_candidate(tree_arg_min(right_border_begin, right)));
1631 }
1632
1633 return valid_value_position(static_cast<std::size_t>(answer.position))
1634 ? static_cast<std::size_t>(answer.position)
1635 : npos;
1636 }
1637
1638 std::span<const T> values_;
1639 Compare compare_;
1640 std::vector<LeafSelector> leaf_selectors_;
1641 std::vector<Bp512Selector> medium_selectors_;
1642 std::vector<T> medium_min_values_;
1643 std::vector<TopCandidate> top_sparse_candidates_;
1644 std::vector<std::size_t> medium_level_offsets_;
1645 std::vector<std::size_t> level_sizes_;
1646 std::vector<std::size_t> level_value_spans_;
1647 std::vector<std::size_t> level_fanouts_;
1648 std::size_t top_block_size_ = kMinTopSparseBlockSize;
1649 std::size_t top_block_count_ = 0;
1650 std::size_t top_sparse_levels_ = 0;
1651};
1652
1653} // namespace pixie::rmq
static std::size_t top_sparse_block_size_for(std::size_t value_count)
Return the top sparse-table block width chosen for a value count.
Definition hybrid_btree.h:212
std::size_t arg_min_impl(std::size_t left, std::size_t right) const
Return the first minimum position in [left, right).
Definition hybrid_btree.h:197
std::size_t top_sparse_block_count() const
Return the current number of top sparse-table blocks.
Definition hybrid_btree.h:239
HybridBTree(HybridBTree &&) noexcept=default
Move an RMQ index while preserving selector and cache storage.
static std::size_t top_sparse_block_count_for(std::size_t value_count)
Return the number of top sparse-table blocks for a value count.
Definition hybrid_btree.h:223
std::size_t size_impl() const
Return the number of indexed values.
Definition hybrid_btree.h:183
HybridBTree()=default
Construct an empty RMQ index.
HybridBTree(const HybridBTree &)=default
Copy an RMQ index while preserving its non-owning value span.
std::size_t memory_usage_bytes_impl() const
Return owned auxiliary memory usage in bytes.
Definition hybrid_btree.h:247
T value_at_impl(std::size_t position) const
Return the value at an indexed position.
Definition hybrid_btree.h:188
std::size_t top_sparse_block_size() const
Return the current top sparse-table block width.
Definition hybrid_btree.h:234
CRTP facade for static range-minimum-query indexes.
Definition rmq.h:28
static constexpr std::size_t npos
Sentinel returned when no valid query answer exists.
Definition rmq.h:33
Definition rmq.h:15
HybridBTreeLeafSelector
Low-level selector implementation used by HybridBTree leaves.
Definition hybrid_btree.h:25
Common interface for static range-minimum-query indexes.