C 将16字节字符串与SSE进行比较

C 将16字节字符串与SSE进行比较,c,gcc,x86,sse,simd,C,Gcc,X86,Sse,Simd,我有16个字节的“字符串”(它们可能较短,但您可以假设它们在末尾用零填充),但您可能不会假设它们是16字节对齐的(至少不总是这样) 如何编写一个将它们与SSE内部函数进行比较(相等)的例程?我发现这个代码片段可能会有所帮助,但我不确定它是否合适 register __m128i xmm0, xmm1; register unsigned int eax; xmm0 = _mm_load_epi128((__m128i*)(a)); xmm1 = _mm_load_epi128((__m1

我有16个字节的“字符串”(它们可能较短,但您可以假设它们在末尾用零填充),但您可能不会假设它们是16字节对齐的(至少不总是这样)

如何编写一个将它们与SSE内部函数进行比较(相等)的例程?我发现这个代码片段可能会有所帮助,但我不确定它是否合适

register __m128i xmm0, xmm1; 
register unsigned int eax; 

xmm0 = _mm_load_epi128((__m128i*)(a)); 
xmm1 = _mm_load_epi128((__m128i*)(b)); 

xmm0 = _mm_cmpeq_epi8(xmm0, xmm1); 

eax = _mm_movemask_epi8(xmm0); 

if(eax==0xffff) //equal 
else   //not equal 
有人能解释一下或者写一个函数体吗


它需要在GCC/mingw(在32位窗口上)中工作。

矢量比较指令根据对应源元素之间的比较,将其结果生成为所有1(真)或所有0(假)元素的掩码

请参阅以获取一些链接,这些链接将告诉您这些内在函数的作用

问题中的代码看起来应该可以工作。

如果要找出哪些元素不相等,请使用movemask版本(
pmovskb
movmskps
)。您可以
tzcnt
/
bsf
对第一个匹配进行位扫描,或
popcnt
对匹配进行计数。所有相等项为您提供一个
0xffff
掩码,非相等项为您提供
0


您可能想知道SSE4.1
ptest
在这里是否有用。它是可用的,但实际上并不是更快,特别是当您在结果上分支而不是将其转换为布尔值0/-1时

 // slower alternative using SSE4.1 ptest
__m128i avec, bvec;
avec = _mm_loadu_si128((__m128i*)(a)); 
bvec = _mm_loadu_si128((__m128i*)(b)); 

__m128i diff = _mm_xor_si128(avec, bvec);  // XOR: all zero only if *a==*b

if(_mm_test_all_zeros(diff, diff))  { //equal 
} else {   //not equal 
}
在asm中,
ptest
是2个UOP,不能与
jcc
条件分支进行宏融合。因此,前端的总
pxor
+
ptest
+分支为4个uops,并且仍然销毁其中一个输入,除非您有AVX将异或结果放入第三个寄存器

pcmpeqb xmm0,xmm1
/
pmovmskb eax,xmm0
/
cmp/jcc
总共是3个uop,其中cmp/jcc在Intel和AMD CPU上融合为1个uop

如果您有更宽的元素,则可以对
pcmpeqd/q
的结果使用
movmskps
movmskpd
来获得4位或2位掩码。如果希望位扫描或popcnt而不除以每个元素4或8个字节,则此选项非常有用。(或使用AVX2、8位或4位而不是32位掩码。)


ptest
只有在不需要任何额外指令来构建输入时才是一个好主意:测试是否为全零,有无掩码。e、 g.检查每个元素或某些元素中的某些位。

嗯,我不确定这是否会更快,但可以通过单个SSE 4.2指令instrinsic完成:检查PCMPISTRI(压缩比较隐式长度字符串,返回索引)的进位和/或溢出标志:

if (_mm_cmpistrc(a, b, mode))   // checks the carry flag (not set = equal)
  // equal
else
  // unequal
模式为(针对您的情况):

不幸的是,此说明的文档记录得很差。
因此,如果有人找到一个合适的资源,将所有模式组合和结果标志聚合在一起,请分享。

我将尝试帮助解决这个问题。有人能解释一下这部分问题吗

register __m128i xmm0, xmm1; 
register unsigned int eax; 
这里我们声明一些变量<代码>\uuum128i是SSE寄存器上整数运算的内置类型。请注意,变量的名称根本不重要,但作者将它们命名为与汇编中调用的相应CPU寄存器完全相同的名称<代码>xmm0,
xmm1
xmm2
xmm3
。。。是SSE操作的所有寄存器
eax
是通用寄存器之一

register
关键字很久以前就被用来建议编译器将变量放入CPU寄存器中。我想今天它完全没用了。有关详细信息,请参阅

xmm0 = _mm_loadu_si128((__m128i*)(a)); 
xmm1 = _mm_loadu_si128((__m128i*)(b)); 
此代码按照@harold的建议进行了修改。这里我们从给定的内存指针(可能未对齐)加载16个字节到变量
xmm0
xmm1
。在汇编代码中,这些变量很可能直接位于寄存器中,因此该内部函数将生成未对齐的内存负载。将指针转换为
\uuu m128i*
类型是必要的,因为Inquired接受这种指针类型,尽管我不知道Intel为什么这样做

xmm0 = _mm_cmpeq_epi8(xmm0, xmm1); 
这里,我们比较
xmm0
变量中的每个字节与
xmm1
变量中的相应字节是否相等。后缀
\u epi8
表示对8位元素(即字节)进行操作。它有点类似于
memcmp(&xmm0,&xmm1,16)
,但会生成其他结果。它返回一个16字节的值,其中包含每个值相等的字节的
0xFF
,以及每个值不同的字节的
0x00

eax = _mm_movemask_epi8(xmm0); 
这是来自SSE2的一条非常重要的指令,通常用于编写带有某种SSE条件的
if
语句。它从XMM参数中的16个字节中取最高的位,并将它们写入一个16位整数。在汇编级别上,这个数字位于通用寄存器中,允许我们稍后快速检查其值

if(eax==0xffff) //equal 
else   //not equal
如果两个XMM寄存器的所有16字节相等,则
\u mm\u cmpeq\u epi8
必须返回一个设置了所有128位的掩码<代码>\u mm\u movemask\u epi8将返回完整的16位掩码,即
0xFFFF
。如果任何两个比较的字节不同,相应的字节将由
\u mm\u cmpeq\u epi8
用零填充,因此
\u mm\u movemask\u epi8
将返回设置了相应位的16位掩码,因此它将小于
0xFFFF

此外,下面是包装到函数中的解释代码:

bool AreEqual(const char *a, const char *b) {
  __m128i xmm0, xmm1; 
  unsigned int eax; 
  xmm0 = _mm_loadu_si128((__m128i*)(a)); 
  xmm1 = _mm_loadu_si128((__m128i*)(b)); 
  xmm0 = _mm_cmpeq_epi8(xmm0, xmm1); 
  eax = _mm_movemask_epi8(xmm0); 
  return (eax == 0xffff); //equal 
}

\u-mm\u-load\u-epi128
不存在,您的意思是要么
\u-mm\u-load\u-si128
,要么因为您说它们可以不对齐,
\u-mm\u-loadu-si128
似乎
ptest
指令在当前的英特尔体系结构上使用了2个UOP。此外,它没有融合后的条件跳转。因此,您的解决方案生成4个UOP,而OP的代码只生成3个UOP。有关详细信息,请参阅。假设这意味着你的解决方案在一个紧密的循环中会变慢。@stgatilov:很好<代码>PTEST可能具有较低的延迟
bool AreEqual(const char *a, const char *b) {
  __m128i xmm0, xmm1; 
  unsigned int eax; 
  xmm0 = _mm_loadu_si128((__m128i*)(a)); 
  xmm1 = _mm_loadu_si128((__m128i*)(b)); 
  xmm0 = _mm_cmpeq_epi8(xmm0, xmm1); 
  eax = _mm_movemask_epi8(xmm0); 
  return (eax == 0xffff); //equal 
}