for 循环的 MMX SSE 扩展

MMX SSE extensions for for loop

我有 GCC 9.2 编译器。 如果我使用 MMX 或 SSE/AVX 扩展,您将并行编码 运行,因此它会更快。 如何告诉编译器使用这个指令 我有一个要并行的代码片段:

char max(char * a, int n){
    char max = (*a);
    for (int i = 0 ; i< n ; ++i){
            if (max < a[i]){
                max = a[i];
            }
    }
    return max;
}

它使用 SSE 扩展生成代码但不使用 pmaxub 为什么

SSE2 是 x86-64 的基准,所以 pmaxub 可用。

但是您的代码在 x86-64 系统 V ABI 中使用 char,并且 char = signed char,并且 Windows x64。也许您来自 char = unsigned char 的 ARM? ISO C 标准保留了 char 实现定义的符号性,因此依赖它来确保正确性(或在这种情况下的性能)是一个糟糕的主意。

如果你像普通人一样使用 uint8_t,即使没有使用 -march=skylake 或任何启用的东西,你也会从 GCC9.2 -O3 获得 x86-64 的预期内循环AVX2。 (Godbolt)

.L14:
        movdqu  xmm2, XMMWORD PTR [rax]
        add     rax, 16
        pmaxub  xmm0, xmm2
        cmp     rax, rdx
        jne     .L14

pmaxsb requires SSE4.1。 (SSE2 与 MMX 一样是高度非正交的,一些操作仅适用于某些大小和符号组合,针对特定应用程序,如音频 DSP 和图形像素。SSE4.1 填补了许多空白。)

如果启用它,GCC 和 clang 将使用它。


仅使用 -O3 和基线 x86-64 -march 默认值(和 -mtune=generic),GCC 使用 pcmpgtb 自动矢量化(这是一个带符号的比较)然后使用 pand/pandn/por 和必要的额外 movdqa 复制进行手动混合。 pcmpgtb 提示您编写的代码需要签名比较,而不是未签名。 Clang 做同样的事情。

.L5:
        movdqu  xmm1, XMMWORD PTR [rax]
        add     rax, 16
        movdqa  xmm2, xmm1
        pcmpgtb xmm2, xmm0
        pand    xmm1, xmm2
        pandn   xmm2, xmm0
        movdqa  xmm0, xmm2
        por     xmm0, xmm1
        cmp     rax, rdx
        jne     .L5

GCC 可以通过 pmaxub 的范围移位输入自动矢量化为无符号,然后通过 adding/subtracting 128.(即 pxor_mm_set1_epi8(0x80))。因此,对于这种情况,这是一个很大的优化失误,它本来可以将关键路径延迟降低到 1 个周期,只是 pmaxub


当然,如果您实际启用 SSE4.1,您会得到 pmaxsb。或者 AVX2 vpmaxsb.

可以 使用 -msse4.1-mavx2 但通常您希望启用更新的 CPU 也有的其他扩展,并设置调整设置。特别是对于 AVX2,您不想针对 Sandybridge 和更旧的 CPU 进行调整,因为 SnB 甚至 没有 AVX2。您不希望拆分未对齐的负载和类似的东西。此外,AVX2 CPUs 通常也有 BMI2、popcnt 和其他好东西。

使用-march=haswell-march=znver1(禅)。或者对于本地使用,-march=native 来优化您的 CPU。 (如果你有 Skylake,它与使用 -march=skylake 相同,除非它可能检测到你的特定 L3 缓存大小或其他东西。)