我正在寻找计算以下函数的有效方法:
输入:__m128i data, uint8_t in
;
输出:指示data
中的任何字节是否in
的布尔值。
我本质上是使用它们来实现容量为 8 的字节的空间/时间高效堆栈。我最有效的解决方案是首先计算一个__m128i tmp
,所有字节都in
。然后检查tmpxor data
中的任何字节是否为零字节。
是的,AVX2 具有高效的字节广播。 带有全零掩码的 SSSE3pshufb
同样便宜,但您必须创建随机控制向量。 AVX512BW/F甚至有单指令vpbroadcastb/w/d/q x/y/zmm, r32
。 (使用可选的掩码,因此您可以根据需要将某些矢量归零或与现有矢量合并,例如使用单位掩码插入某个位置。
幸运的是,编译器在实现_mm_set1_epi8
时知道如何做到这一点,因此我们可以将其留给编译器。
然后它只是归结为通常的pcmpeqb
/pmovmskb
得到一个整数,该整数将具有用于匹配元素的1
位,您可以对其进行分支。
// 0 for not found, non-zero for found. (Bit position tells you where).
unsigned contains(__m128i data, uint8_t needle) {
__m128i k = _mm_set1_epi8(needle);
__m128i cmp = _mm_cmpeq_epi8(data, k); // vector mask
return _mm_movemask_epi8(cmp); // integer bitmask
}
正如你所期望的,所有的编译器都使用这个asm(Godbolt)。
contains(long long __vector(2), unsigned char):
vmovd xmm1, edi
vpbroadcastb xmm1, xmm1
vpcmpeqb xmm0, xmm0, xmm1
vpmovmskb eax, xmm0
ret
除了MSVC,它首先浪费了movsx eax, dl
的指令。 (Windows x64 在 RDX 中传递第二个参数,而 x86-64 System V 在 RDI 中传递第一个整数参数。
如果没有AVX2,你会得到类似的东西与SSSE3或更高版本
。# gcc8.3 -O3 -march=nehalem
contains(long long __vector(2), unsigned char):
movd xmm1, edi
pxor xmm2, xmm2
pshufb xmm1, xmm2 # _mm_shuffle_epi8(needle, _mm_setzero_si128())
pcmpeqb xmm0, xmm1
pmovmskb eax, xmm0
ret
或者仅使用 SSE2(x86-64 的基线):
contains(long long __vector(2), unsigned char):
mov DWORD PTR [rsp-12], edi
movd xmm1, DWORD PTR [rsp-12] # gcc's tune=generic strategy is still store/reload /facepalm
punpcklbw xmm1, xmm1 # duplicate to low 2 bytes
punpcklwd xmm1, xmm1 # duplciate to low 4 bytes
pshufd xmm1, xmm1, 0 # broadcast
pcmpeqb xmm1, xmm0
pmovmskb eax, xmm1
ret
相关:
- 如何使用 SIMD 比较两个向量并获得单个布尔结果? 和许多重复项
如何使用 SIMD 计算数组中字节的出现次数?
SIMD/SSE:如何检查所有矢量元素是否为非零 (
pxor
+ptest
+jcc
= 4 uops 与pcmpeqb
+pmovmskb
+ 宏融合test/jcc
= 3 uops。SSE/AVX 寄存器的非零字节索引(查找匹配位置)
如何使用 SIMD 计算字符出现次数(与 memchr 类似,但使用 AVX2 计算匹配项而不是查找第一个匹配项)。 具有有效的计数累积和有效的水平总和。