17#include <simd/integer.h>
30 namespace fixed_search_detail {
31 inline std::uint32_t
load_word(std::byte
const *source)
noexcept {
33 std::memcpy(&value, source,
sizeof(value));
34 if constexpr (std::endian::native == std::endian::big) {
35 value = ((value & 0x00ff00ffu) << 8) | ((value >> 8) & 0x00ff00ffu);
36 value = (value << 16) | (value >> 16);
41 template <std::
size_t Words, std::
size_t Lanes>
42 inline std::array<std::uint32_t, Lanes>
repeated(std::array<std::uint32_t, Words>
const &query)
noexcept {
43 std::array<std::uint32_t, Lanes> result{};
44 for (std::size_t i = 0; i < Lanes; ++i) result[i] = query[i % Words];
48 template <std::
size_t Words,
bool Upper>
49 inline bool precedes(std::byte
const *source, std::array<std::uint32_t, Words>
const &query)
noexcept {
50 bool less =
false, equal =
true;
51 for (std::size_t i = 0; i < Words; ++i) {
52 auto value =
load_word(source + (i << 2));
53 less |= equal & (value < query[i]);
54 equal &= value == query[i];
56 if constexpr (Upper)
return less | equal;
60 template <
bool Upper, simd::architecture Arch>
61 simd_inline
bool precedes_four(std::byte
const *source, simd::vec<std::uint32_t, 4, Arch> query)
noexcept {
62 using V = simd::vec<std::uint32_t, 4, Arch>;
63 auto value = V::load(
reinterpret_cast<std::uint32_t
const *
>(source));
66 auto order = mask_bits(value > query) - mask_bits(value < query);
67 auto weighted = order * V(std::array<std::uint32_t, 4>{8, 4, 2, 1});
68 auto comparison = std::bit_cast<std::int32_t>(std::uint32_t(reduce_add_widened(weighted)));
69 if constexpr (Upper)
return comparison >= 0;
70 else return comparison > 0;
76 template <std::
size_t Words,
bool Upper, simd::architecture Arch>
77 simd_inline
unsigned population(std::byte
const *source,
unsigned count,
78 std::array<std::uint32_t, Words>
const &query)
noexcept {
80 if constexpr (std::is_same_v<Arch, simd::scalar> || std::endian::native != std::endian::little) {
82 for (
unsigned i = 0; i < count; ++i) result += precedes<Words, Upper>(source + i * Words * 4, query);
84 }
else if constexpr (std::is_same_v<Arch, simd::neon>) {
85 if constexpr (Words == 1) {
86 using V = simd::vec<std::uint32_t, 4, Arch>;
87 auto values = V::load_partial(
reinterpret_cast<std::uint32_t
const *
>(source), count);
88 auto queries = V(query[0]);
89 auto active = V(std::array<std::uint32_t, 4>{0, 1, 2, 3}) < V(count);
90 auto matches = Upper ? values <= queries : values < queries;
91 return unsigned(reduce_add_widened(select(matches &
active, V(1), V(0))));
92 }
else if constexpr (Words == 2) {
93 using W = simd::vec<std::uint32_t, 4, Arch>;
94 using V = simd::vec<std::uint64_t, 2, Arch>;
95 auto queries = V((std::uint64_t(query[0]) << 32) | query[1]);
97 for (
unsigned at = 0; at < 4; at += 2) {
98 if (at >= count)
break;
99 if (count - at == 1) result += precedes<Words, Upper>(source + (at << 3), query);
101 auto words = W::load(
reinterpret_cast<std::uint32_t
const *
>(source + (at << 3)));
102 W swapped = words.yxwz;
103 auto values = reinterpret_bits<std::uint64_t>(swapped);
104 auto matches = Upper ? values <= queries : values < queries;
105 auto counts = mask_bits(matches).template right<63>();
106 result += unsigned(reduce_add_widened(reinterpret_bits<std::uint32_t>(counts)));
111 auto queries = simd::vec<std::uint32_t, 4, Arch>::load(query.data());
113 for (
unsigned at = 0; at < 4; ++at) {
114 if (at >= count)
break;
115 result += precedes_four<Upper, Arch>(source + (at << 4), queries);
120 unsigned less = 0, equal = 0;
121 unsigned word_count = count * unsigned(Words);
122 constexpr unsigned lanes = unsigned(backend_detail::register_bytes<Arch> /
sizeof(std::uint32_t));
123 using V = simd::vec<std::uint32_t, lanes, Arch>;
124 auto queries = V(repeated<Words, lanes>(query));
125 for (
unsigned at = 0; at < Words * 4; at += lanes) {
126 if (at >= word_count)
break;
127 auto values = V::load_partial(
reinterpret_cast<std::uint32_t
const *
>(source + (at << 2)),
128 std::min(lanes, word_count - at));
129 less |= unsigned((values < queries).to_bitset()) << at;
130 equal |= unsigned((values == queries).to_bitset()) << at;
132 constexpr unsigned starts = Words == 1 ? 0xffffu : Words == 2 ? 0x5555u : 0x1111u;
133 unsigned prefix = starts & ((1u << word_count) - 1);
134 unsigned matches = prefix, result = 0;
135 for (
unsigned word = 0; word < Words; ++word) {
136 result |= matches & (less >> word);
137 matches &= equal >> word;
139 if constexpr (Upper) result |= matches;
140 return unsigned(std::popcount(result));
149 template <std::
size_t Words, simd::architecture Arch = simd::scalar>
struct fixed_key_view {
150 static_assert(Words == 1 || Words == 2 || Words == 4,
"fixed keys have 1, 2, or 4 words");
158 error_detail::raise<std::invalid_argument>(
"fixed-key extent is not a whole number of keys");
162 std::span<std::byte const>
bytes() const noexcept {
return bytes_; }
164 if (ordinal >=
size()) error_detail::raise<std::out_of_range>(
"fixed-key ordinal");
171 if (first >
size() || count >
size() - first)
172 error_detail::raise<std::out_of_range>(
"fixed-key subview");
176 return prefer_simd() ? simd_bound<false>(query) : binary_bound<false>(query);
179 return prefer_simd() ? simd_bound<true>(query) : binary_bound<true>(query);
190 if constexpr (std::is_same_v<Arch, simd::neon> && std::endian::native == std::endian::little) {
191 if constexpr (Words == 4)
193 else return size() == 4;
194 }
else if constexpr (std::is_same_v<Arch, simd::avx2> && std::endian::native == std::endian::little) {
195 if constexpr (Words == 1)
return size() == 4;
196 else if constexpr (Words == 2)
return size() >= 2 &&
size() <= 4;
197 else return size() == 2 ||
size() == 4;
202 std::size_t first = 0;
203 auto step = std::bit_floor(count);
204 if (std::has_single_bit(count + 1)) {
206 for (; step; step >>= 1)
207 first += step * fixed_search_detail::precedes<Words, Upper>(
210 for (; step; step >>= 1) {
211 auto next = first + step;
212 auto probe = std::min(next, count) - 1;
213 auto below = fixed_search_detail::precedes<Words, Upper>(
bytes_.data() + probe *
key_bytes, query);
214 first += step * ((next <= count) & below);
219 template <
bool Upper> simd_inline
void pivot_step(
key_type const &query, std::size_t &first, std::size_t &count)
const noexcept {
221 std::array<std::size_t, 3> positions{count >> 2, count >> 1, (3 * count) >> 2};
223 if constexpr (Words == 4 && std::is_same_v<Arch, simd::neon> && std::endian::native == std::endian::little) {
224 auto queries = simd::vec<std::uint32_t, 4, Arch>::load(query.data());
226 for (
unsigned i = 0; i < 3; ++i)
227 rank += fixed_search_detail::precedes_four<Upper, Arch>(
bytes_.data() + (first + positions[i]) *
key_bytes, queries);
230 std::array<std::byte, 3 * key_bytes> pivots;
231 for (
unsigned i = 0; i < 3; ++i)
233 rank = fixed_search_detail::population<Words, Upper, Arch>(pivots.data(), 3, query);
235 auto start = ((rank * count) >> 2) + (rank != 0);
236 auto end = ((rank + 1) * count) >> 2;
240 template <
bool Upper,
unsigned Bucket> simd_inline std::size_t
small_bound(
key_type const &query)
const noexcept {
241 std::size_t first = 0, count =
size();
242 if constexpr (Bucket > 4) pivot_step<Upper>(query, first, count);
244 if constexpr (Bucket > 16) pivot_step<Upper>(query, first, count);
245 if (!count)
return first;
246 return first + fixed_search_detail::population<Words, Upper, Arch>(
bytes_.data() + first *
key_bytes,
unsigned(count), query);
249 if (
size() <= 4)
return small_bound<Upper, 4>(query);
250 if (
size() <= 8)
return small_bound<Upper, 8>(query);
251 if (
size() <= 16)
return small_bound<Upper, 16>(query);
253 return binary_bound<Upper>(query);
Shares explicit SIMD architecture traits with Everett's kernels.
Outlines exceptional check failures while preserving their types and messages.
unsigned population(std::byte const *source, unsigned count, std::array< std::uint32_t, Words > const &query) noexcept
Definition fixed_search.h:77
std::uint32_t load_word(std::byte const *source) noexcept
Definition fixed_search.h:31
bool precedes_four(std::byte const *source, simd::vec< std::uint32_t, 4, Arch > query) noexcept
Definition fixed_search.h:61
std::array< std::uint32_t, Lanes > repeated(std::array< std::uint32_t, Words > const &query) noexcept
Definition fixed_search.h:42
bool precedes(std::byte const *source, std::array< std::uint32_t, Words > const &query) noexcept
Definition fixed_search.h:49
Definition active_engine.h:18
Definition fixed_search.h:149
std::size_t small_bound(key_type const &query) const noexcept
Definition fixed_search.h:240
std::span< std::byte const > bytes_
Definition fixed_search.h:186
std::size_t upper_bound_binary(key_type const &query) const noexcept
Definition fixed_search.h:182
Arch architecture
Definition fixed_search.h:151
bool empty() const noexcept
Definition fixed_search.h:161
std::size_t upper_bound_simd(key_type const &query) const noexcept
Definition fixed_search.h:184
std::size_t lower_bound_simd(key_type const &query) const noexcept
Definition fixed_search.h:183
std::size_t upper_bound(key_type const &query) const noexcept
Definition fixed_search.h:178
static constexpr std::size_t explicit_simd_limit
Definition fixed_search.h:154
bool prefer_simd() const noexcept
Definition fixed_search.h:187
void pivot_step(key_type const &query, std::size_t &first, std::size_t &count) const noexcept
Definition fixed_search.h:219
fixed_key_view subview(std::size_t first, std::size_t count) const
Definition fixed_search.h:170
key_type key_at(std::size_t ordinal) const
Definition fixed_search.h:163
std::size_t lower_bound_binary(key_type const &query) const noexcept
Definition fixed_search.h:181
static constexpr std::size_t key_bytes
Definition fixed_search.h:153
fixed_key_view(std::span< std::byte const > bytes)
Definition fixed_search.h:156
std::size_t binary_bound(key_type const &query) const noexcept
Definition fixed_search.h:200
std::size_t simd_bound(key_type const &query) const noexcept
Definition fixed_search.h:248
std::array< std::uint32_t, Words > key_type
Definition fixed_search.h:152
std::size_t lower_bound(key_type const &query) const noexcept
Definition fixed_search.h:175
std::size_t size() const noexcept
Definition fixed_search.h:160
std::span< std::byte const > bytes() const noexcept
Definition fixed_search.h:162