C++ 使用SSE通过uint64[]进行线性搜索

C++ 使用SSE通过uint64[]进行线性搜索,c++,c,search,sse,linear-search,C++,C,Search,Sse,Linear Search,我正在尝试使用SSE通过uint64数组实现线性搜索 说明书我为uint16和uint32做了一些工作,但我得到了编译器 uint64代码的错误(linux、gcc-请参阅最后的规范) 我试图比较2x2 64位数字,然后以某种方式转换结果 在我的数组的索引中。这与uint32配合得很好(学分归 ): 错误显示: error: cannot convert 'vec1uint64 {aka __vector(2) long unsigned int}' to '__vector(2) int' f

我正在尝试使用SSE通过uint64数组实现线性搜索 说明书我为uint16和uint32做了一些工作,但我得到了编译器 uint64代码的错误(linux、gcc-请参阅最后的规范)

我试图比较2x2 64位数字,然后以某种方式转换结果 在我的数组的索引中。这与uint32配合得很好(学分归 ):

错误显示:

error: cannot convert 'vec1uint64 {aka __vector(2) long unsigned int}'
to '__vector(2) int' for argument '1' to '__vector(4) short int
__builtin_ia32_packssdw(__vector(2) int, __vector(2) int)'
(在uint32代码中,vec2uint64的typedef位于顶部。)

我的环境:

Linux ws4484 3.5.0-48-generic #72~precise1-Ubuntu SMP Tue Mar 11 20:09:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5)
我的问题不仅仅是我如何修复编译器错误,而是如果有人有更好的主意来获得匹配的数组索引,也许没有整个数组 包装物


提前谢谢

我找不到任何将64位整数转换为32位整数的指令,这正是您需要使用packssdw等的指令。它变得相当长和混乱,但应该可以工作:

因此,我认为解决方案是使用位掩码(位0、1、2、3:

这些在循环之前:

vec2uint64 mask0 = { 2, 1 };
vec2uint64 mask1 = { 8, 4 };
vec2uint64 zero  = { 0, 0 };
内部循环:

vec2uint64 res0 = _mm_and_si128(cmp0, mask0);
vec2uint64 res1 = _mm_and_si128(cmp1, mask1);

vec2uint64 res2 = _mm_or_si128(res0, res1);
然后我们需要将一个新变量的高部分换成低部分:

vec2uint64 hi0 = _mm_unpackhi_epi64(res0, zero);
vec2uint64 hi1 = _mm_unpackhi_epi64(res1, zero);

vec2uint64 hi2 = _mm_or_si128(hi0, hi1);
vec2uint64 res3 = _mm_or_si128(res2, hi2);
现在res的低位64位[好吧,低位4位,其余为零]是一个位掩码,其中一个值匹配

int res = _mm_cvtsi128_si32(res3);

(现在您可以像以前一样计算尾随的零)。

我建议不要使用内置的内部函数和隐式向量。这只有在您不使用非GCC内部函数(例如,_mm_cmpeq_epi32)并且只想坚持使用GCC时才有意义。您可以这样做

__m128i key2 = _mm_set1_epi64x(key);
__m128i v1 = _mm_loadu_si128((const __m128i *)&data[start + i + 0]);
__m128i v2 = _mm_loadu_si128((const __m128i *)&data[start + i + 2]);

__m128i cmp0 = _mm_cmpeq_epi64(key2, v1);
__m128i cmp1 = _mm_cmpeq_epi64(key2, v2);

__m128i low2  = _mm_shuffle_epi32(cmp0,0xD8);  
__m128i high2 = _mm_shuffle_epi32(cmp1,0xD8);      
__m128i pack = _mm_unpacklo_epi64(low2,high2);

__m128i pack01 = _mm_packs_epi32(pack, _mm_setzero_si128());
__m128i pack0123 = _mm_packs_epi16(pack01, _mm_setzero_si128());

int res =  _mm_movemask_epi8(pack0123);
您可能会找到一个更高效的版本来避免打包,但是您必须使用不同于
\uuuu builtin\uctz
的功能

对于32位整数,我建议

__m128i key4 = _mm_set1_epi32(key);
__m128i v1 = _mm_loadu_si128((const __m128i *)&data[start + i + 0]);
__m128i v2 = _mm_loadu_si128((const __m128i *)&data[start + i + 4]);
__m128i v3 = _mm_loadu_si128((const __m128i *)&data[start + i + 8]);
__m128i v4 = _mm_loadu_si128((const __m128i *)&data[start + i + 12]);

__m128i cmp0 = _mm_cmpeq_epi32(key4, v1);
__m128i cmp1 = _mm_cmpeq_epi32(key4, v2);
__m128i cmp2 = _mm_cmpeq_epi32(key4, v3);
__m128i cmp3 = _mm_cmpeq_epi32(key4, v4);

__m128i pack01 = _mm_packs_epi32(cmp0, cmp1);
__m128i pack23 = _mm_packs_epi32(cmp2, cmp3);
__m128i pack0123 = _mm_packs_epi16(pack01, pack23);

int res = _mm_movemask_epi8(pack0123);

这是一个用于搜索64位值以查找匹配项的完整解决方案。在本例中,该值(namehash)是一个结构成员。此例程在每次迭代中比较8个64位值,并提供匹配的结构索引

//ptr is a struct array
__m128i key2 = _mm_set1_epi64x(k); //k is the 64 bit search key
for(;;)
   {
   if(!ptr[i].namehash)return NULL;
   __m128i v1 = _mm_set_epi64x (ptr[i+1].namehash,ptr[i].namehash);
   __m128i v2 = _mm_set_epi64x (ptr[i+3].namehash,ptr[i+2].namehash);
   __m128i v3 = _mm_set_epi64x (ptr[i+5].namehash,ptr[i+4].namehash);
   __m128i v4 = _mm_set_epi64x (ptr[i+7].namehash,ptr[i+6].namehash);

   __m128i cmp0 = _mm_cmpeq_epi64(key2, v1);
   __m128i cmp1 = _mm_cmpeq_epi64(key2, v2);
   __m128i cmp2 = _mm_cmpeq_epi64(key2, v3);
   __m128i cmp3 = _mm_cmpeq_epi64(key2, v4);

   __m128i L0  = _mm_shuffle_epi32(cmp0,0xD8);
   __m128i H1 = _mm_shuffle_epi32(cmp1,0xD8);
   __m128i L2  = _mm_shuffle_epi32(cmp2,0xD8);
   __m128i H3 = _mm_shuffle_epi32(cmp3,0xD8);

   __m128i pack0 = _mm_unpacklo_epi64(L0,H1);
   __m128i pack1 = _mm_unpacklo_epi64(L2,H3);

   __m128i pack01 = _mm_packs_epi32(pack0,pack1);
   __m128i pack0123 = _mm_packs_epi16(pack01, _mm_setzero_si128());

   res =  _mm_movemask_epi8(pack0123);
   if(res > 0)break;
   i+=8;
   }

int index = i + __builtin_ctz(res);  //The struct table index to the matching struct.
重要提示:结构数组长度必须是8的倍数,后面至少有一个NULL成员

或者,如果每次迭代只需要2个64位比较,则可以将上述内容大大简化为:

for(;;)
   {
   if(!ptr[i].namehash)return NULL;
   __m128i v1 = _mm_set_epi64x (ptr[i+1].namehash,ptr[i].namehash);
   __m128i cmp0 = _mm_cmpeq_epi64(key2, v1);
   res =  _mm_movemask_epi8(cmp0);
   if(res > 0)break;
   i+=2;
   }
int ctz = __builtin_ctz(res);
int index = i + (ctz>>5);  //The struct table index to the matching struct.

为什么要使用低级的内部函数,比如
\uuuu builtin\u ia32\u packssdw
,而不是更常见的
\umm\u packs\u epi32
?这会让处理向量类型变得更加混乱。没有具体的原因;我不知道如何使用\umm\u packs\u epi32。谢谢你的评论,我会替换它。如果你一直使用普通的高级内部函数然后你可以使用<代码> > M128i <代码>,以不必担心混合32位和64位操作的不同类型,正如你所看到的,可以变得丑陋。我遵照你的建议。谢谢你的帮助和评论!你是用C写的还是C++的?把两个SSE登记器的四个64位值打包成四个32。一个SSE寄存器中的位值可以使用三个内部变量完成:
uu m128i low2=mm\u shuffle\u epi32(cmp0,0xD8);uuu m128i high2=mm\u shuffle\u epi32(cmp1,0xD8);mm\u unplo\u epi64(low2,high2)
。我尝试了这个函数,但它并没有为我的所有测试提供一致的结果。它确实找到了结果,但尾随的零的数量不再正确。不过,感谢您的回答,我非常感谢。这非常有效。也感谢32位建议-我已经根据@Paul R的建议替换了内部函数。我想GCC内置的内部函数还有其他优点。例如,对于SSE2,_mm_cmpeq_epi64需要SSE4.1,这很复杂,GCC内部函数会为您解决这一问题。对于大型阵列,如果您只需将cmp结果或cmp结果放在一起,就可以加快内部循环的速度,以查看是否存在任何匹配。然后在循环外部,找出ma的位置tch使用了shuffle/pack(这和它一样,但更像是一场胜利,因为所有的shuffle都很昂贵。)另外,如果您想要一个小的位掩码,那么使用带有零的
\u mm\u packs\u epi32
似乎是一种巨大的浪费;只需使用
\u mm\u movemask\u ps
来获取每个dword元素的高位。对于大数组,如果您只是将cmp结果或cmp结果放在一起以查看是否存在任何匹配,则可以使内部循环更快。然后在循环之外,找出比赛是用洗牌/pack(这和它一样,但更像是一场胜利,因为所有洗牌都很昂贵。)此外,用零打包的最后一步可以通过使用
(\uuu builtin\u ctz(res)>>1)
来完成,如果你不想将循环优化为4x pcmp+3x por+pmovskb,那么就在循环之外
//ptr is a struct array
__m128i key2 = _mm_set1_epi64x(k); //k is the 64 bit search key
for(;;)
   {
   if(!ptr[i].namehash)return NULL;
   __m128i v1 = _mm_set_epi64x (ptr[i+1].namehash,ptr[i].namehash);
   __m128i v2 = _mm_set_epi64x (ptr[i+3].namehash,ptr[i+2].namehash);
   __m128i v3 = _mm_set_epi64x (ptr[i+5].namehash,ptr[i+4].namehash);
   __m128i v4 = _mm_set_epi64x (ptr[i+7].namehash,ptr[i+6].namehash);

   __m128i cmp0 = _mm_cmpeq_epi64(key2, v1);
   __m128i cmp1 = _mm_cmpeq_epi64(key2, v2);
   __m128i cmp2 = _mm_cmpeq_epi64(key2, v3);
   __m128i cmp3 = _mm_cmpeq_epi64(key2, v4);

   __m128i L0  = _mm_shuffle_epi32(cmp0,0xD8);
   __m128i H1 = _mm_shuffle_epi32(cmp1,0xD8);
   __m128i L2  = _mm_shuffle_epi32(cmp2,0xD8);
   __m128i H3 = _mm_shuffle_epi32(cmp3,0xD8);

   __m128i pack0 = _mm_unpacklo_epi64(L0,H1);
   __m128i pack1 = _mm_unpacklo_epi64(L2,H3);

   __m128i pack01 = _mm_packs_epi32(pack0,pack1);
   __m128i pack0123 = _mm_packs_epi16(pack01, _mm_setzero_si128());

   res =  _mm_movemask_epi8(pack0123);
   if(res > 0)break;
   i+=8;
   }

int index = i + __builtin_ctz(res);  //The struct table index to the matching struct.
for(;;)
   {
   if(!ptr[i].namehash)return NULL;
   __m128i v1 = _mm_set_epi64x (ptr[i+1].namehash,ptr[i].namehash);
   __m128i cmp0 = _mm_cmpeq_epi64(key2, v1);
   res =  _mm_movemask_epi8(cmp0);
   if(res > 0)break;
   i+=2;
   }
int ctz = __builtin_ctz(res);
int index = i + (ctz>>5);  //The struct table index to the matching struct.