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

标签 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((__m128i*)(b)); 

xmm0 = _mm_cmpeq_epi8(xmm0, xmm1); 

eax = _mm_movemask_epi8(xmm0); 

if(eax==0xffff) //equal 
else   //not equal 

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

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

最佳答案

vector 比较指令根据相应源元素之间的比较,将其结果作为掩码,由全 1(真)或全 0(假)的元素组成。

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

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

如果您想找出哪些元素不相等,请使用 movemask 版本( pmovmskbmovmskps )。您可以 tzcnt/bsf 对第一次匹配进行位扫描,或 popcnt 对匹配进行计数。 All-equal 给你一个 0xffff 掩码,non-equal 给你 0

您可能想知道 SSE4.1 ptest 在这里是否有用。它可用,但实际上并没有更快,特别是如果您对结果进行分支而不是将其转换为 bool 值 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 uops,并且不能与 jcc 条件分支进行宏融合。因此,前端的总 pxor + ptest + branch 是 4 uops,并且仍然会破坏其中一个输入,除非您有 AVX 将异或结果放入第三个寄存器。
pcmpeqb xmm0, xmm1/pmovmskb eax, xmm0/cmp/jcc 总共是 3 uop,在 Intel 和 AMD CPU 上,cmp/jcc 融合为 1 uop。

如果您有更宽的元素,您可以对 movmskps 的结果使用 movmskpdpcmpeqd/q 以获得 4 位或 2 位掩码。如果您想在不为每个元素除以 4 或 8 个字节的情况下进行位扫描或 popcnt,这将非常有用。 (或者使用 AVX2,8 位或 4 位而不是 32 位掩码。)

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

关于将 16 字节字符串与 SSE 进行比较,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31999284/

相关文章:

C程序从文件中读取特定行

assembly - 直接读取程序计数器

assembly - 8086 汇编语言可以在现代 CPU 上运行吗?

C 使用 openssl 执行 HTTPS 请求

c - 在远程系统上构建时,Postgres 的 Initdb 失败并出现误导性错误消息

c# - 使用使用不同编译器编译的 DLL

c++ - libstdc++ 和 libc++ : operator>> on bitset 行为差异

c++ - 接受指针参数的 GCC 纯/常量函数

c++ - 仅在 Linux 操作系统上出现双指针后段错误

assembly - 为什么编译器在除以 2 时会产生 31 位的右移?