C 将16字节字符串与SSE进行比较
我有16个字节的“字符串”(它们可能较短,但您可以假设它们在末尾用零填充),但您可能不会假设它们是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
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
}