使用 SSE/AVX/AVX2 检查 __m128i 的所有字节是否匹配单个字节

Check all bytes of a __m128i for a match of a single byte using SSE/AVX/AVX2

我正在寻找计算以下函数的有效方法:

输入:__m128i data, uint8_t in

输出:布尔值,指示 data 中的任何字节是否为 in

我实际上是在使用它们为容量为 8 的字节实现一个 space/time 高效堆栈。我最有效的解决方案是首先计算一个 __m128i tmp,所有字节都为 in .然后检查 tmp\xor data 中的任何字节是否为零字节。

是的,AVX2 具有高效的字节广播。带有全零掩码的 SSSE3 pshufb 同样便宜,但您必须创建洗牌控制向量。 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 中传递第一个 integer arg。)


如果没有 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

相关:

  • How to compare two vectors using SIMD and get a single boolean result? 和许多重复项
  • pxor+ptest+jcc = 4 微指令对比 pcmpeqb+pmovmskb +宏融合 test/jcc = 3 uops。)

  • The indices of non-zero bytes of an SSE/AVX register(找到匹配位置)

  • How to count character occurrences using SIMD(类似于 memchr,但使用 AVX2 计算匹配项而不是查找第一个匹配项。具有有效的计数累积和有效的水平总和。)