@@ -45,6 +45,10 @@ static inline const __m256i mask_first_half = _mm256_setr_epi8(
4545// clang-format on
4646#endif
4747
48+ #if defined(__BMI2__) && !defined(PIXIE_DISABLE_BMI2)
49+ #define PIXIE_BMI2_SUPPORT
50+ #endif
51+
4852/* *
4953 * @brief Test 16 int16 RmM btree child ranges for a node-local target.
5054 * @details Each lane represents one child summary. The function checks whether
@@ -235,37 +239,144 @@ static inline uint64_t rank_512(const uint64_t* x, uint64_t count) {
235239#endif
236240}
237241
242+ #ifndef PIXIE_BMI2_SUPPORT
243+ struct PixieSelectByteLut {
244+ uint8_t popcounts[256 ];
245+ uint8_t select[256 ][8 ];
246+
247+ constexpr PixieSelectByteLut () : popcounts{}, select{} {
248+ for (int byte = 0 ; byte < 256 ; ++byte) {
249+ for (int rank = 0 ; rank < 8 ; ++rank) {
250+ select[byte][rank] = 8 ;
251+ }
252+
253+ int count = 0 ;
254+ for (int bit = 0 ; bit < 8 ; ++bit) {
255+ if (((byte >> bit) & 1 ) != 0 ) {
256+ select[byte][count++] = static_cast <uint8_t >(bit);
257+ }
258+ }
259+ popcounts[byte] = static_cast <uint8_t >(count);
260+ }
261+ }
262+ };
263+
264+ static inline constexpr PixieSelectByteLut pixie_select_byte_lut;
265+
266+ static inline uint64_t select_64_no_bmi2 (uint64_t x, uint64_t rank) {
267+ uint64_t offset = 0 ;
268+
269+ uint64_t count = std::popcount (static_cast <uint32_t >(x));
270+ if (rank >= count) {
271+ rank -= count;
272+ x >>= 32 ;
273+ offset += 32 ;
274+ }
275+
276+ count = std::popcount (static_cast <uint16_t >(x));
277+ if (rank >= count) {
278+ rank -= count;
279+ x >>= 16 ;
280+ offset += 16 ;
281+ }
282+
283+ const auto low_byte = static_cast <uint8_t >(x);
284+ count = pixie_select_byte_lut.popcounts [low_byte];
285+ if (rank >= count) {
286+ rank -= count;
287+ x >>= 8 ;
288+ offset += 8 ;
289+ }
290+
291+ return offset + pixie_select_byte_lut.select [static_cast <uint8_t >(x)][rank];
292+ }
293+ #endif
294+
238295/* *
239296 * @brief Return position of @p rank 1 bit in @p x
240297 */
241298static inline uint64_t select_64 (uint64_t x, uint64_t rank) {
242- return _tzcnt_u64 (_pdep_u64 (1ull << rank, x));
299+ #ifdef PIXIE_BMI2_SUPPORT
300+ return std::countr_zero (_pdep_u64 (1ull << rank, x));
301+ #else
302+ return select_64_no_bmi2 (x, rank);
303+ #endif
243304}
244305
245- /* *
246- * @brief Return position of @p rank 1 bit in @p x
247- * @details Selecting within 64-bit word can be done
248- * using combination of _tzcnt_u64(_pdep_u64(1 << rank, x))
249- * See Pandey P., Bender M. A., Johnson R. A fast x86 implementation of select
250- * https://arxiv.org/abs/1706.00990
251- *
252- * To find a 64-bit word inside a 512-bit region we use
253- * We first popcounts of all 8 64-bit words with _mm512_popcnt_epi64
254- * and then perform a linear scan.
255- *
256- * Notably a SWAR algorithm for parallel binary search
257- * http://www-graphics.stanford.edu/~seander/bithacks.html#SelectPosFromMSBRank
258- * might be used as a backoff algorithm for selecting in a 64-bit word.
259- * It can also be used as an alternative for linear search but i don't
260- * see a proper SIMD algorithm to make it faster.
261- */
262- static inline uint64_t select_512 (const uint64_t * x, uint64_t rank) {
263- #ifdef PIXIE_AVX512_SUPPORT
306+ template <bool Invert>
307+ static inline uint64_t select_512_word_count (uint64_t word) {
308+ if constexpr (Invert) {
309+ return std::popcount (~word);
310+ } else {
311+ return std::popcount (word);
312+ }
313+ }
264314
265- __m512i res = _mm512_loadu_epi64 (x);
266- __m512i counts = _mm512_popcnt_epi64 (res);
267- __m512i prefix = counts;
315+ template <bool Invert>
316+ static inline uint64_t select_512_selected_word (uint64_t word) {
317+ if constexpr (Invert) {
318+ return ~word;
319+ } else {
320+ return word;
321+ }
322+ }
323+
324+ template <bool Invert>
325+ static inline uint64_t select_512_scalar_impl (const uint64_t * x,
326+ uint64_t rank) {
327+ for (size_t i = 0 ; i < 8 ; ++i) {
328+ const uint64_t count = select_512_word_count<Invert>(x[i]);
329+ if (rank < count) {
330+ return i * 64 + select_64 (select_512_selected_word<Invert>(x[i]), rank);
331+ }
332+ rank -= count;
333+ }
334+ return 512 ;
335+ }
268336
337+ #ifdef PIXIE_AVX2_SUPPORT
338+ template <bool Invert>
339+ static inline void select_512_avx2_counts (const uint64_t * x, uint64_t * counts) {
340+ const __m256i low_mask = _mm256_set1_epi8 (0x0F );
341+ const __m256i zero = _mm256_setzero_si256 ();
342+ const __m256i sixty_four = _mm256_set1_epi64x (64 );
343+
344+ for (int half = 0 ; half < 2 ; ++half) {
345+ const __m256i words =
346+ _mm256_loadu_si256 (reinterpret_cast <const __m256i*>(x + 4 * half));
347+
348+ const __m256i low_nibbles = _mm256_and_si256 (words, low_mask);
349+ const __m256i high_nibbles =
350+ _mm256_and_si256 (_mm256_srli_epi16 (words, 4 ), low_mask);
351+ const __m256i byte_counts =
352+ _mm256_add_epi8 (_mm256_shuffle_epi8 (lookup_popcount_4, low_nibbles),
353+ _mm256_shuffle_epi8 (lookup_popcount_4, high_nibbles));
354+ __m256i word_counts = _mm256_sad_epu8 (byte_counts, zero);
355+ if constexpr (Invert) {
356+ word_counts = _mm256_sub_epi64 (sixty_four, word_counts);
357+ }
358+ _mm256_store_si256 (reinterpret_cast <__m256i*>(counts + 4 * half),
359+ word_counts);
360+ }
361+ }
362+
363+ template <bool Invert>
364+ static inline uint64_t select_512_avx2_impl (const uint64_t * x, uint64_t rank) {
365+ alignas (32 ) uint64_t counts[8 ];
366+ select_512_avx2_counts<Invert>(x, counts);
367+
368+ for (size_t i = 0 ; i < 8 ; ++i) {
369+ if (rank < counts[i]) {
370+ return i * 64 + select_64 (select_512_selected_word<Invert>(x[i]), rank);
371+ }
372+ rank -= counts[i];
373+ }
374+ return 512 ;
375+ }
376+ #endif
377+
378+ #ifdef PIXIE_AVX512_SUPPORT
379+ static inline __m512i select_512_avx512_prefix_sum_u64 (__m512i prefix) {
269380 const __m512i idx_shift1 = _mm512_set_epi64 (6 , 5 , 4 , 3 , 2 , 1 , 0 , 0 );
270381 const __m512i idx_shift2 = _mm512_set_epi64 (5 , 4 , 3 , 2 , 1 , 0 , 0 , 0 );
271382 const __m512i idx_shift4 = _mm512_set_epi64 (3 , 2 , 1 , 0 , 0 , 0 , 0 , 0 );
@@ -275,76 +386,67 @@ static inline uint64_t select_512(const uint64_t* x, uint64_t rank) {
275386 tmp = _mm512_maskz_permutexvar_epi64 (0xFC , idx_shift2, prefix);
276387 prefix = _mm512_add_epi64 (prefix, tmp);
277388 tmp = _mm512_maskz_permutexvar_epi64 (0xF0 , idx_shift4, prefix);
278- prefix = _mm512_add_epi64 (prefix, tmp);
389+ return _mm512_add_epi64 (prefix, tmp);
390+ }
279391
280- __mmask8 mask = _mm512_cmpgt_epu64_mask (prefix, _mm512_set1_epi64 (rank));
281- uint32_t i = _tzcnt_u32 (static_cast <uint32_t >(mask));
282- uint64_t prev = 0 ;
283- if (i != 0 ) {
284- __m512i idx_prev = _mm512_set1_epi64 (static_cast <int64_t >(i - 1 ));
285- __m512i prev_vec = _mm512_permutexvar_epi64 (idx_prev, prefix);
286- prev = static_cast <uint64_t >(
287- _mm_cvtsi128_si64 (_mm512_castsi512_si128 (prev_vec)));
392+ static inline uint64_t select_512_avx512_previous_prefix (__m512i prefix,
393+ uint32_t lane) {
394+ if (lane == 0 ) {
395+ return 0 ;
288396 }
289- return i * 64 + select_64 (x[i], rank - prev);
290-
291- #else
397+ const __m512i idx_previous =
398+ _mm512_set1_epi64 (static_cast <int64_t >(lane - 1 ));
399+ const __m512i previous_vec = _mm512_permutexvar_epi64 (idx_previous, prefix);
400+ return static_cast <uint64_t >(
401+ _mm_cvtsi128_si64 (_mm512_castsi512_si128 (previous_vec)));
402+ }
292403
293- size_t i = 0 ;
294- int popcount = std::popcount (x[0 ]);
295- while (i < 7 && popcount <= rank) {
296- rank -= popcount;
297- popcount = std::popcount (x[++i]);
404+ template <bool Invert>
405+ static inline uint64_t select_512_avx512_impl (const uint64_t * x,
406+ uint64_t rank) {
407+ const __m512i words = _mm512_loadu_epi64 (x);
408+ __m512i prefix = _mm512_popcnt_epi64 (words);
409+ if constexpr (Invert) {
410+ prefix = _mm512_sub_epi64 (_mm512_set1_epi64 (64 ), prefix);
298411 }
299- return i * 64 + select_64 (x[i], rank);
412+ prefix = select_512_avx512_prefix_sum_u64 (prefix);
413+
414+ const __mmask8 mask = _mm512_cmpgt_epu64_mask (
415+ prefix, _mm512_set1_epi64 (static_cast <int64_t >(rank)));
416+ const uint32_t lane = std::countr_zero (static_cast <uint32_t >(mask));
417+ const uint64_t previous = select_512_avx512_previous_prefix (prefix, lane);
418+ return lane * 64 +
419+ select_64 (select_512_selected_word<Invert>(x[lane]), rank - previous);
420+ }
421+ #endif
300422
423+ /* *
424+ * @brief Return position of @p rank 1 bit in @p x.
425+ * @details Uses AVX-512, then AVX2, then scalar fallback. The 64-bit in-word
426+ * select uses BMI2 PDEP unless PIXIE_DISABLE_BMI2 is defined or BMI2 is not
427+ * available.
428+ */
429+ static inline uint64_t select_512 (const uint64_t * x, uint64_t rank) {
430+ #ifdef PIXIE_AVX512_SUPPORT
431+ return select_512_avx512_impl<false >(x, rank);
432+ #elif defined(PIXIE_AVX2_SUPPORT)
433+ return select_512_avx2_impl<false >(x, rank);
434+ #else
435+ return select_512_scalar_impl<false >(x, rank);
301436#endif
302437}
303438
304439/* *
305- * @brief Return position of @p rank0 0 bit in @p x
440+ * @brief Return position of @p rank0 0 bit in @p x.
306441 * @details select_512 with bit inversion.
307442 */
308443static inline uint64_t select0_512 (const uint64_t * x, uint64_t rank0) {
309444#ifdef PIXIE_AVX512_SUPPORT
310-
311- __m512i res = _mm512_loadu_epi64 (x);
312- __m512i counts =
313- _mm512_sub_epi64 (_mm512_set1_epi64 (64 ), _mm512_popcnt_epi64 (res));
314- __m512i prefix = counts;
315-
316- const __m512i idx_shift1 = _mm512_set_epi64 (6 , 5 , 4 , 3 , 2 , 1 , 0 , 0 );
317- const __m512i idx_shift2 = _mm512_set_epi64 (5 , 4 , 3 , 2 , 1 , 0 , 0 , 0 );
318- const __m512i idx_shift4 = _mm512_set_epi64 (3 , 2 , 1 , 0 , 0 , 0 , 0 , 0 );
319-
320- __m512i tmp = _mm512_maskz_permutexvar_epi64 (0xFE , idx_shift1, prefix);
321- prefix = _mm512_add_epi64 (prefix, tmp);
322- tmp = _mm512_maskz_permutexvar_epi64 (0xFC , idx_shift2, prefix);
323- prefix = _mm512_add_epi64 (prefix, tmp);
324- tmp = _mm512_maskz_permutexvar_epi64 (0xF0 , idx_shift4, prefix);
325- prefix = _mm512_add_epi64 (prefix, tmp);
326-
327- __mmask8 mask = _mm512_cmpgt_epu64_mask (prefix, _mm512_set1_epi64 (rank0));
328- uint32_t i = _tzcnt_u32 (static_cast <uint32_t >(mask));
329- uint64_t prev = 0 ;
330- if (i != 0 ) {
331- __m512i idx_prev = _mm512_set1_epi64 (static_cast <int64_t >(i - 1 ));
332- __m512i prev_vec = _mm512_permutexvar_epi64 (idx_prev, prefix);
333- prev = static_cast <uint64_t >(
334- _mm_cvtsi128_si64 (_mm512_castsi512_si128 (prev_vec)));
335- }
336- return i * 64 + select_64 (~x[i], rank0 - prev);
337-
445+ return select_512_avx512_impl<true >(x, rank0);
446+ #elif defined(PIXIE_AVX2_SUPPORT)
447+ return select_512_avx2_impl<true >(x, rank0);
338448#else
339-
340- size_t i = 0 ;
341- int popcount = std::popcount (~x[0 ]);
342- while (i < 7 && popcount <= rank0) {
343- rank0 -= popcount;
344- popcount = std::popcount (~x[++i]);
345- }
346- return i * 64 + select_64 (~x[i], rank0);
347-
449+ return select_512_scalar_impl<true >(x, rank0);
348450#endif
349451}
350452
0 commit comments