是否有一个内在函数可以将 __m128i 向量的最后 n 个字节归零?

Ron*_*rma 2 c sse simd vectorization

鉴于n,我想将向量的最后一个n字节归零__m128i

例如,考虑以下__m128i向量:

11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111

将最后一个n = 4字节归零后,向量应如下所示:

11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 00000000 00000000 00000000 00000000

是否有执行此操作的 SSE 内在函数(通过接受__128i向量和n作为参数)?

har*_*old 5

有多种不依赖于 AVX512 的选项。例如:

未对齐的负载

char mask[32] = { 0, 0, 0, 0, 0, 0, 0, 0,
                  0, 0, 0, 0, 0, 0, 0, 0,
                  -1, -1, -1, -1, -1, -1, -1, -1,
                  -1, -1, -1, -1, -1, -1, -1, -1};

__m128i zeroLowestNBytes(__m128i x, uint32_t n)
{
    __m128i m = _mm_loadu_si128((__m128i*)&mask[16 - n]);
    return _mm_and_si128(x, m);
}
Run Code Online (Sandbox Code Playgroud)

使用 AVX,负载可以成为vpand. 没有 AVX,它仍然可以,有movdqupand

未对齐的负载通常不是问题,除非它跨越 4K 边界。如果你能得到mask32 对齐,那么这个问题就会消失。负载仍会未对齐,但不会达到特定的边缘情况。

nuint32_t为了避免符号扩展。

广播和比较

__m128i zeroLowestNBytes(__m128i x, int n)
{
    __m128i threshold = _mm_set1_epi8(n);
    __m128i index = _mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
    return _mm_andnot_si128(_mm_cmpgt_epi8(threshold, index), x);
}
Run Code Online (Sandbox Code Playgroud)

这避免了未对齐的负载,但这并不重要。更重要的是,它避免了“依赖于输入的负载”:在具有未对齐负载的版本中,负载取决于n. 在这个版本中,负载独立于n. 例如,如果此函数是内联的,则允许编译器将其提升出循环。它还允许乱序执行更自由地尽早开始加载,也许在n计算之前。

另一方面,它基本上需要 AVX2 或 SSSE3 才能实现_mm_set1_epi8(n). 此外,这通常会花费更多指令,这可能会降低吞吐量。延迟应该更好,因为“主链”中没有负载(有负载,但它不在一边,它不会将其延迟添加到计算的延迟中)。

  • `alignas(32) char mask[] = {...}` 是 ISO C11(带有 `#include <stdalign.h>` 以及标准 ISO C++11。 (4认同)
  • 对于带有全 0 向量的“pshufb”来说,“_mm_set1_epi8”对于仅 SSSE3 来说还不错。与“vpbroadcastb”相比,它只需要 1 个额外的“pxor”归零指令。如果关键路径是通过“__m128i x”,而不是“int n”,那么任何一种方法都可以。 (3认同)