110 HybridBTree<T, Compare, Index, LeafSize, Fanout, LeafSelectorKind>,
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 "
126 static_assert(kBpLeafSelector || kMaskLeafSelector,
127 "unsupported HybridBTree low-level selector kind");
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;
175 explicit
HybridBTree(std::span<const T> values, Compare compare = Compare())
176 : values_(values), compare_(compare) {
183 std::size_t
size_impl()
const {
return values_.size(); }
198 if (left >= right || right > values_.size() || level_sizes_.empty()) {
202 if (right - left <= top_block_size_) {
203 return tree_arg_min(left, right);
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);
213 if (value_count == 0) {
214 return kMinTopSparseBlockSize;
216 return std::max(kMinTopSparseBlockSize,
217 1 + (value_count - 1) / kMaxTopSparseBlocks);
224 if (value_count == 0) {
228 return 1 + (value_count - 1) / block_size;
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_);
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;
277 static_assert(kEmbeddedOffsetBit + 8 == kSelectorBits);
278 static_assert(kEmbeddedPositionBit + 128 == kSelectorBits);
279 static_assert(!kBpLeafSelector || LeafSize <= kEmbeddedOffsetEntries);
280 static_assert(kMiddleFanout <= kEmbeddedPositionEntries);
282 struct MinCandidate {
283 std::size_t position = npos;
284 const T* value =
nullptr;
287 struct TopCandidate {
288 Index position = invalid_index;
290 static_assert(
sizeof(TopCandidate) ==
sizeof(Index));
292 class alignas(64) Bp512Selector {
297 Bp512Selector() =
default;
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");
314 if (entry_count == 0) {
318 std::array<std::uint16_t, kSelectorEntries> stack{};
319 std::size_t stack_size = 0;
320 std::size_t write_position = 2 * entry_count;
322 for (std::size_t i = entry_count; i-- > 0;) {
323 while (stack_size != 0 && !entry_less(stack[stack_size - 1], i)) {
325 prepend_bp_bit(write_position,
true);
327 stack[stack_size++] =
static_cast<std::uint16_t
>(i);
328 prepend_bp_bit(write_position,
false);
331 while (write_position != 0) {
332 prepend_bp_bit(write_position,
true);
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) {
349 if (slot_left + 1 == slot_right) {
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) {
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) {
366 const std::size_t zero_rank = rank0_at(shifted_min, bit_count);
367 if (zero_rank == 0) {
370 const std::size_t entry = zero_rank - 1;
371 return entry < entry_count ? entry : npos;
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) {
388 if (slot_left + 1 == slot_right) {
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) {
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) {
407 const std::size_t zero_rank =
408 rank0_at_with_zero_prefix(shifted_min, bit_count);
409 if (zero_rank == 0) {
412 const std::size_t entry = zero_rank - 1;
413 return entry < entry_count ? entry : npos;
419 std::size_t close_position(std::size_t slot)
const {
420 return select0_512(bp_bits_.data(), slot);
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);
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));
444 std::uint8_t embedded_min_offset()
const {
445 return static_cast<std::uint8_t
>(
446 (bp_bits_[kEmbeddedOffsetBit >> 6] & kEmbeddedOffsetMask) >>
447 (kEmbeddedOffsetBit & 63));
453 void set_embedded_min_position(std::size_t position) {
454 bp_bits_[kEmbeddedPositionBit >> 6] =
455 static_cast<std::uint64_t
>(position);
461 std::size_t embedded_min_position()
const {
462 return static_cast<std::size_t
>(bp_bits_[kEmbeddedPositionBit >> 6]);
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) {
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);
483 bp_bits_[kMiddleZeroPrefixWord] = packed;
490 std::size_t prepend_bp_bit(std::size_t& write_position,
bool bit) {
493 bp_bits_[write_position >> 6] |= std::uint64_t{1}
494 << (write_position & 63);
496 return write_position;
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);
511 std::size_t zero_prefix_at_word(std::size_t word)
const {
512 return static_cast<std::uint8_t
>(bp_bits_[kMiddleZeroPrefixWord] >>
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);
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));
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) {
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);
569 std::size_t depth_arg_min(std::size_t left,
571 std::size_t bit_count)
const {
572 const std::size_t depth_count = bit_count + 1;
573 if (left >= right || right > depth_count) {
577 std::size_t position = left;
578 int best_depth = prefix_excess(position);
579 std::size_t best_position = position;
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;
588 std::size_t candidate_position;
589 if (chunk_begin >= bit_count) {
590 candidate_depth = prefix_excess(chunk_begin);
591 candidate_position = chunk_begin;
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;
600 if (candidate_depth < best_depth) {
601 best_depth = candidate_depth;
602 best_position = candidate_position;
605 position = chunk_begin + local_right + 1;
608 return best_position;
614 std::size_t depth_arg_min_with_zero_prefix(std::size_t left,
616 std::size_t bit_count)
const {
617 const std::size_t depth_count = bit_count + 1;
618 if (left >= right || right > depth_count) {
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;
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;
633 std::size_t candidate_position;
634 if (chunk_begin >= bit_count) {
636 prefix_excess_with_zero_prefix(chunk_begin, bit_count);
637 candidate_position = chunk_begin;
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);
643 prefix_excess_with_zero_prefix(chunk_begin, bit_count) +
645 candidate_position = chunk_begin + result.offset;
648 if (candidate_depth < best_depth) {
649 best_depth = candidate_depth;
650 best_position = candidate_position;
653 position = chunk_begin + local_right + 1;
656 return best_position;
659 std::array<std::uint64_t, kSelectorWords> bp_bits_{};
662 static_assert(
sizeof(Bp512Selector) == 64);
664 class alignas(LeafSize == 496 ? 64 : 32) PrefixSuffixMaskLeafSelector {
669 PrefixSuffixMaskLeafSelector() =
default;
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");
686 if (entry_count == 0) {
687 set_embedded_min_offset(0);
691 std::size_t prefix_best = 0;
693 for (std::size_t slot = 1; slot < entry_count; ++slot) {
694 if (entry_less(slot, prefix_best)) {
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;) {
704 if (!entry_less(suffix_best, slot)) {
710 set_embedded_min_offset(prefix_best);
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) {
728 if (slot_left + 1 == slot_right) {
732 const std::size_t min_offset = embedded_min_offset();
733 if (slot_left <= min_offset && min_offset < slot_right) {
736 if (slot_left == 0 && slot_right <= min_offset) {
737 return previous_set_bit_before(slot_right);
739 if (slot_right == entry_count && min_offset < slot_left) {
740 return next_set_bit_at_or_after(slot_left);
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);
757 std::size_t embedded_min_offset()
const {
758 return static_cast<std::size_t
>((words_[kOffsetWord] >> kOffsetShift) &
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;
773 static constexpr std::uint64_t low_bits_mask(std::size_t bits) {
778 return std::numeric_limits<std::uint64_t>::max();
780 return (std::uint64_t{1} << bits) - 1;
783 static constexpr std::uint64_t kOffsetMask = low_bits_mask(kOffsetBits);
784 static constexpr std::uint64_t kMaskWordMask = low_bits_mask(kOffsetShift);
786 static_assert(!kMaskLeafSelector || kPackedBits % 64 == 0);
787 static_assert(!kMaskLeafSelector || kOffsetShift + kOffsetBits == 64);
792 void set_mask_bit(std::size_t slot) {
793 words_[slot >> 6] |= std::uint64_t{1} << (slot & 63);
799 std::uint64_t mask_word(std::size_t word)
const {
800 return word == kOffsetWord ? words_[word] & kMaskWordMask : words_[word];
806 std::size_t previous_set_bit_before(std::size_t limit)
const {
811 std::size_t word = (limit - 1) >> 6;
813 mask_word(word) & first_bits_mask(((limit - 1) & 63) + 1);
816 return word * 64 + 63 - std::countl_zero(bits);
822 bits = mask_word(word);
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) {
835 const std::size_t result = word * 64 + std::countr_zero(bits);
836 return result < kMaskEntries ? result : npos;
839 bits = word < kWordCount ? mask_word(word) : 0;
844 std::array<std::uint64_t, kWordCount> words_{};
847 static_assert(!kMaskLeafSelector ||
sizeof(PrefixSuffixMaskLeafSelector) ==
848 (LeafSize == 496 ? 64 : 32));
849 static_assert(!kMaskLeafSelector ||
alignof(PrefixSuffixMaskLeafSelector) ==
850 (LeafSize == 496 ? 64 : 32));
852 using LeafSelector = std::conditional_t<kMaskLeafSelector,
853 PrefixSuffixMaskLeafSelector,
859 bool missing_position(std::size_t position)
const {
860 if constexpr (kInvalidIndexEqualsNpos) {
861 return position == npos;
863 return position == npos ||
864 position ==
static_cast<std::size_t
>(invalid_index);
871 MinCandidate value_candidate(std::size_t position)
const {
872 if (missing_position(position) || position >= values_.size()) {
875 return {position, values_.data() + position};
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)) {
887 return {position, &subtree_min_value(level, node)};
893 MinCandidate subtree_child_min_candidate(std::size_t level,
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);
904 bool strictly_better_candidate(MinCandidate left, MinCandidate right)
const {
905 if (missing_position(left.position)) {
908 if (missing_position(right.position)) {
911 return compare_(*left.value, *right.value);
917 MinCandidate better_candidate(MinCandidate left, MinCandidate right)
const {
918 if (missing_position(left.position)) {
921 if (missing_position(right.position)) {
924 if (compare_(*right.value, *left.value)) {
927 if (compare_(*left.value, *right.value)) {
930 return right.position < left.position ? right : left;
936 bool strictly_better_subtree_child_slot(std::size_t level,
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));
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()) {
963 if (values_.size() >
static_cast<std::size_t
>(invalid_index)) {
964 throw std::length_error(
"HybridBTree index type is too small");
967 initialize_layout((values_.size() + LeafSize - 1) / LeafSize);
968 for (std::size_t leaf = 0; leaf < level_sizes_[0]; ++leaf) {
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);
977 build_top_sparse_table();
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);
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);
1001 leaf_selectors_.resize(level_sizes_[0]);
1003 medium_level_offsets_.assign(level_count(), 0);
1004 if (level_count() <= 1) {
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];
1013 medium_selectors_.resize(medium_node_count);
1014 medium_min_values_.reserve(medium_node_count);
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]);
1028 if constexpr (kBpLeafSelector) {
1029 const std::size_t slot = selector.arg_min(0, count, count);
1030 selector.set_embedded_min_offset(slot);
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);
1042 selector.build(count, [&](std::size_t left, std::size_t right) {
1043 return strictly_better_subtree_child_slot(level, node, left, right);
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);
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();
1061 return left * right;
1067 static std::size_t ceil_div(std::size_t value, std::size_t divisor) {
1068 return value == 0 ? 0 : 1 + (value - 1) / divisor;
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);
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);
1086 const auto [level, node_index] = covering_node(left_leaf, right_leaf);
1087 return query_node(level, node_index, left, right).position;
1097 std::size_t leaf_range_min(std::size_t leaf,
1099 std::size_t right)
const {
1100 if (left >= right) {
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);
1109 const std::size_t slot_left = left - begin;
1110 const std::size_t slot_right = right - begin;
1112 if constexpr (kMaskLeafSelector) {
1113 const std::size_t slot = leaf_selectors_[leaf].arg_min(
1114 slot_left, slot_right, entry_count(0, leaf));
1116 return begin + slot;
1118 return linear_range_min(left, right);
1120 if (right - left <= kLeafLinearScanThreshold) {
1121 return linear_range_min(left, right);
1124 const std::size_t slot = leaf_selectors_[leaf].arg_min(
1125 slot_left, slot_right, entry_count(0, leaf));
1129 return begin + slot;
1136 std::size_t linear_range_min(std::size_t left, std::size_t right)
const {
1137 if (left >= right) {
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);
1148 std::size_t best = left;
1149 for (std::size_t position = left + 1; position < right; ++position) {
1150 if (compare_(values_[position], values_[best])) {
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;
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));
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);
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),
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;
1209 for (; position < right; ++position) {
1210 if (data[position] < best_value) {
1211 best_value = data[position];
1212 best_position = position;
1215 return best_position;
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) {
1230 const std::size_t fanout = fanout_at_level(level);
1231 left_node /= fanout;
1232 right_node /= fanout;
1234 return {level, left_node};
1240 std::size_t leaf_for_value(std::size_t position)
const {
1241 return position / LeafSize;
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];
1260 MinCandidate query_child_slots(std::size_t level,
1262 std::size_t slot_left,
1263 std::size_t slot_right,
1265 std::size_t right)
const {
1266 if (slot_left >= slot_right) {
1270 const std::size_t count = entry_count(level, node);
1271 const std::size_t slot =
1272 slot_left + 1 == slot_right
1274 : selector_arg_min(level, node, slot_left, slot_right, count);
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)) {
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));
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)));
1310 if (slot_left + 1 < last_slot) {
1311 answer = better_candidate(
1313 full_child_slot_range_min(level, node, slot_left + 1, last_slot));
1322 MinCandidate full_child_slot_range_min(std::size_t level,
1324 std::size_t slot_left,
1325 std::size_t slot_right)
const {
1326 if (slot_left >= slot_right) {
1330 const std::size_t slot =
1331 slot_left + 1 == slot_right
1333 : selector_arg_min(level, node, slot_left, slot_right,
1334 entry_count(level, node));
1339 return subtree_child_min_candidate(level, node, slot);
1345 MinCandidate query_node(std::size_t level,
1348 std::size_t right)
const {
1349 if (left >= right) {
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);
1358 return value_candidate(leaf_range_min(node, left, right));
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);
1373 bool contains_position(std::size_t left,
1375 std::size_t position)
const {
1376 return !missing_position(position) && left <= position && position < right;
1382 std::size_t level_count()
const {
return level_sizes_.size(); }
1387 std::size_t entry_count(std::size_t level, std::size_t node)
const {
1389 const std::size_t begin = node_value_begin(0, node);
1390 return std::min<std::size_t>(LeafSize, values_.size() - begin);
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);
1400 std::size_t node_value_begin(std::size_t level, std::size_t node)
const {
1401 return node * level_value_spans_[level];
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]);
1415 std::size_t subtree_min_position(std::size_t level, std::size_t node)
const {
1417 return node_value_begin(0, node) +
1418 leaf_selectors_[node].embedded_min_offset();
1420 return selector_at(level, node).embedded_min_position();
1426 const T& subtree_min_value(std::size_t level, std::size_t node)
const {
1428 return values_[subtree_min_position(0, node)];
1430 return medium_min_values_[medium_flat_index(level, node)];
1436 const Bp512Selector& selector_at(std::size_t level, std::size_t node)
const {
1437 return medium_selectors_[medium_flat_index(level, node)];
1443 Bp512Selector& mutable_selector_at(std::size_t level, std::size_t node) {
1444 return medium_selectors_[medium_flat_index(level, node)];
1450 std::size_t selector_arg_min(std::size_t level,
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);
1462 std::size_t medium_flat_index(std::size_t level, std::size_t node)
const {
1463 return medium_level_offsets_[level] + node;
1469 std::size_t fanout_at_level(std::size_t level)
const {
1470 return level_fanouts_[level];
1476 void build_top_sparse_table() {
1477 top_sparse_candidates_.clear();
1480 top_sparse_levels_ =
1481 top_block_count_ == 0 ? 0 : std::bit_width(top_block_count_);
1482 if (top_block_count_ == 0) {
1486 top_sparse_candidates_.assign(top_sparse_levels_ * top_block_count_,
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)) {
1497 top_sparse_candidates_[block] = make_top_candidate(minimum);
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) {
1509 better_top_candidate(previous[block], previous[block + half_span]);
1517 TopCandidate make_top_candidate(std::size_t position)
const {
1518 if (!valid_value_position(position)) {
1521 return {
static_cast<Index
>(position)};
1527 bool valid_value_position(std::size_t position)
const {
1528 return !missing_position(position) && position < values_.size();
1535 bool strictly_better_value_position(std::size_t left,
1536 std::size_t right)
const {
1537 if (!valid_value_position(left)) {
1540 if (!valid_value_position(right)) {
1543 if (compare_(values_[left], values_[right])) {
1546 if (compare_(values_[right], values_[left])) {
1549 return left < right;
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
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) {
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]);
1583 bool top_candidate_inside(TopCandidate candidate,
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 &&
1594 std::size_t top_sparse_arg_min(std::size_t left, std::size_t right)
const {
1595 if (top_block_count_ <= 1) {
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) {
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);
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) {
1618 TopCandidate answer =
1619 top_sparse_block_arg_min(first_full_block, full_block_right);
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)));
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)));
1633 return valid_value_position(
static_cast<std::size_t
>(answer.position))
1634 ?
static_cast<std::size_t
>(answer.position)
1638 std::span<const T> values_;
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;