C SIMD:在数组中累积UINT64_T元素的爆米花

C++ SIMD: accumulate POPCOUNTs of uint64_t elements in an array

本文关键字:UINT64 元素 爆米花 SIMD 数组      更新时间:2023-10-16

我试图使用SSE指令在数组中为uint64_t整数累积POPCOUNT s。
这是我的代码:

#include <emmintrin.h>
#include <nmmintrin.h>
#include <chrono>
int main()
{    
  uint64_t data[4] = {1,1,1,1};
  uint64_t data2[4] = {1,0,1,0};
  __m128i* ptr = (__m128i*) data;
  __m128i* ptr2 = (__m128i*) data2;
  int total = 0;
  for (int i = 0; i < 2; ++i, ++ptr, ++ptr2)
    total += popcount(_mm_and_si128(*ptr, *ptr2)); // This doesn't work    
}

我需要在_mm_and_si128输出上运行的POPCOUNT函数的等效物,因此我可以将所有POPCOUNT s累积到total变量中。

我们拥有有关此主题的整个研究论文:使用AVX2指令进行更快的人群计数。尽管有标题,但它也涵盖了SSE。有关相关的软件库,请参见Hamming_Weight。它包括各种快速功能来执行此类工作。

简短的答案:您可以像这样使用Muła爆炸式函数:

 __m128i popcount(__m128i v) {
    const __m128i lookup = _mm_setr_epi8(
    /* 0 */ 0, /* 1 */ 1, /* 2 */ 1, /* 3 */ 2,
    /* 4 */ 1, /* 5 */ 2, /* 6 */ 2, /* 7 */ 3,
    /* 8 */ 1, /* 9 */ 2, /* a */ 2, /* b */ 3,
    /* c */ 2, /* d */ 3, /* e */ 3, /* f */ 4
);
   __m128i low_mask = _mm_set1_epi8(0x0f);
   __m128i lo  = _mm_and_si128(v, low_mask);
   __m128i hi  = _mm_and_si128(_mm_srli_epi16(v, 4), low_mask);
   __m128i popcnt1 = _mm_shuffle_epi8(lookup, lo);
   __m128i popcnt2 = _mm_shuffle_epi8(lookup, hi);
  return _mm_sad_epu8(_mm_add_epi8(popcnt1, popcnt2), _mm_setzero_si128());
}

popcount调用的结果是由两个64位计数器制成的128位计数器,您必须添加。总结两个64位计数器可以在结束时节省计算时间。

POPCOUNT与SSE寄存器不起作用。您需要将_mm_and_sil128的结果保存到内存,然后在两个半部分使用POPCOUNT_mm_popcnt_u64),因为POPCOUNT指令最多仅限于64位操作数。