与 SSE 比较 16 字节字符串

use*_*913 5 c x86 gcc sse simd

我有 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 
Run Code Online (Sandbox Code Playgroud)

有人可以解释一下或者写一个函数体吗?

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

Pet*_*des 5

向量比较指令根据相应源元素之间的比较将其结果生成为全 1(真)或全 0(假)元素的掩码。

请参阅https://stackoverflow.com/tags/x86/info获取一些链接,这些链接将告诉您这些内在函数的作用。

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

如果您想找出哪些元素不相等,请使用 movemask 版本(pmovmskbmovmskps)。您可以tzcnt/bsf对第一个匹配项进行位扫描,或popcnt对匹配项进行计数。全部平等给你一个0xffff面具,非平等给你一个0


您可能想知道 SSE4.1ptest在这里是否有用。它是可用的,但实际上并没有更快,特别是如果您对结果进行分支而不是将其转换为布尔值 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 
}
Run Code Online (Sandbox Code Playgroud)

在 asm 中,ptest是 2 uops,并且不能与jcc条件分支进行宏融合。因此,前端的总pxor++ptest分支为 4 uops,并且仍然会破坏输入之一,除非您有 AVX 将异或结果放入第三个寄存器中。

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

如果您有更宽的元素,则可以在 的结果上使用movmskps或来获取 4 位或 2 位掩码。如果您想要位扫描或 popcnt 而不将每个元素除以 4 或 8 字节,则这是最有用的。(或者使用 AVX2、8 位或 4 位而不是 32 位掩码。)movmskpdpcmpeqd/q

ptest只有当您不需要任何额外的指令来为其构建输入时,这才是一个好主意:测试是否全零,带或不带掩码。例如,检查每个元素或某些元素中的某些位。

  • 看起来“ptest”指令在当前的 Intel 架构上使用 2 uops。而且,它**不**与后面的条件跳转融合。因此,您的解决方案生成 4 uops,而 OP 的代码仅生成 3 uops。有关详细信息,请参阅[此问题](https://software.intel.com/en-us/forums/topic/293050)。据推测,这意味着您的解决方案在紧密循环中会变慢。 (2认同)
  • 在用“ptest”替换“pmovmskb”后,我观察到实际基准测试和 IACA 分析结果中的吞吐量都降低了。似乎只有当你绝对需要检查寄存器的**所有** 128 位是否为零时,`ptest` 指令才有用(这种情况很少发生)。如果您正在进行任何比较(这是最流行的情况),那么在其之后使用“ptest”是有害的。好吧,也许它的延迟较低,谁知道呢...... (2认同)