编译器在普通的C代码中是否使用SSE指令?

16

我看到人们默认使用-msse -msse2 -mfpmath=sse标志,希望这样可以提高性能。我知道当在C代码中使用特殊向量类型时,SSE会被启用。但是这些标志对于常规的C代码有任何区别吗?编译器是否使用SSE来优化常规的C代码?


3
所有x86-64(也称为AMD64)架构都具有SSE和SSE2。因此,如果编译目标是x86-64,编译器将使用SSE寄存器和SSE/SSE2扩展。一般来说,C编译器在向量化代码方面的表现相对较差,所以大多数情况下编译器会为x86-64上的“普通C”生成非向量化的SSE/SSE2代码。(我将其写成评论而不是答案,因为我不确定您确切想要什么样的回答。) - Nominal Animal
我想问的是:是否合理将这些标志提供给clang或gcc,并期望未按矢量化类型编写的代码获得性能提升。在这种情况下,编译器是否真正使用SSE指令来矢量化某些内容? - Jennifer M.
这取决于目标架构和使用的其他标志。它们绝对有助于32位Intel(x86)架构(代码可以在任何支持32位代码的操作系统/内核上运行,或者任何支持SSE2的x86处理器)。对于x86-64目标,它们是多余的(这些标志默认情况下已生效,基于目标)。如果编译器知道SSE / SSE2可用(基于体系结构或选项标志),它将尽力矢量化甚至“常规C”;总的来说,它们并不是非常好。 - Nominal Animal
1个回答

20
是的,如果使用全优化编译,则现代编译器会自动使用SSE2进行向量化。Clang在-O2处向量化循环,gcc在-O3处向量化循环。 (GCC12在-O2时启用向量化,但仅当成本非常低廉时, 仍需要-O3来向量化大多数带有运行时变量trip counts的循环。)
即使在-O1-Os下,编译器也会使用SIMD加载/存储指令来复制或初始化比整数寄存器宽的结构体或其他对象。这并不真正算作自动向量化;它更像是默认内置的memset/memcpy策略的一部分,用于小型固定大小块。(如果没有-fno-builtin,这也将适用于小常量长度的显式使用memcpy。)它确实利用和需要支持SIMD指令并由内核启用,无论您是否称其为“向量化”。(内核使用-mgeneral-regs-only,或在旧版GCC中使用-mno-mmx -mno-sse来禁用此功能。)

SSE2是x86-64的基线/非可选项,因此编译器在针对x86-64时总是可以使用SSE1/SSE2指令。稍后的指令集(SSE4、AVX、AVX2、AVX512以及像BMI2、popcnt等非SIMD扩展)必须手动启用(例如-march=x86-64-v3-msse4.1),告诉编译器可以生成不能在旧CPU上运行的代码。或者生成多个版本的代码并在运行时进行选择,但这会增加额外的开销,只有对于较大的函数才值得。

-msse -msse2 -mfpmath=sse已经是x86-64的默认设置,但不适用于32位i386。一些32位调用约定在x87寄存器中返回FP值,因此使用SSE/SSE2进行计算然后必须将结果存储/重新加载到x87 st(0)中可能不方便。使用-mfpmath=sse,更智能的编译器可能仍会使用x87进行产生FP返回值的计算。

在32位x86上,默认情况下可能没有启用-msse2,这取决于编译器的配置方式。如果您正在使用32位,因为您的目标CPU太旧而无法运行64位代码,则可能希望确保禁用它,或仅使用-msse

使用-O3 -march=native -mfpmath=sse可以让二进制文件针对你编译的CPU进行调优,同时使用链接时优化和基于性能的优化。在测试数据上运行gcc -fprofile-generate,然后运行gcc -fprofile-use。使用-march=native会使得二进制文件无法在旧的CPU上运行,因为编译器可能使用了新的指令。对于gcc来说,基于性能的优化非常有帮助:如果没有它,它永远不会展开循环。但是通过PGO,它知道哪些循环经常运行/迭代很多次,即哪些循环是“热点”,值得花费更多的代码大小进行优化。链接时优化允许在文件之间进行内联和常量传播。如果你拥有许多小函数却没有在头文件中定义,它非常有帮助。

请参考如何从GCC / clang汇编输出中去除“噪音”?,了解更多有关查看编译器输出并理解其含义的内容。

这里有一些关于x86-64的具体例子在Godbolt编译器浏览器上。Godbolt还支持其他几种架构的gcc,而且使用clang时可以添加-target mips或其他选项,因此您还可以看到启用了正确编译器选项的ARM NEON的自动向量化。您可以使用-m32与x86-64编译器一起生成32位代码。
int sumint(int *arr) {
    int sum = 0;
    for (int i=0 ; i<2048 ; i++){
        sum += arr[i];
    }
    return sum;
}

使用gcc8.1 -O3进行内部循环(不使用-march=haswell或任何启用AVX / AVX2的选项):

.L2:                                 # do {
    movdqu  xmm2, XMMWORD PTR [rdi]    # load 16 bytes
    add     rdi, 16
    paddd   xmm0, xmm2                 # packed add of 4 x 32-bit integers
    cmp     rax, rdi
    jne     .L2                      # } while(p != endp)

    # then horizontal add and extract a single 32-bit sum

没有-ffast-math,编译器无法重新排序FP操作,因此float等效项不会自动矢量化(参见Godbolt链接:您将获得标量addss)。(OpenMP可以在每个循环基础上启用它,或使用-ffast-math)。

但是,一些FP内容可以安全地自动矢量化而不改变操作顺序。

// clang won't contract this into an FMA without -ffast-math :/
// but gcc will (if you compile with -march=haswell)
void scale_array(float *arr) {
    for (int i=0 ; i<2048 ; i++){
        arr[i] = arr[i] * 2.1f + 1.234f;
    }
}

  # load constants: xmm2 = {2.1,  2.1,  2.1,  2.1}
  #                 xmm1 = (1.23, 1.23, 1.23, 1.23}
.L9:   # gcc8.1 -O3                       # do {
    movups  xmm0, XMMWORD PTR [rdi]         # load unaligned packed floats
    add     rdi, 16
    mulps   xmm0, xmm2                      # multiply Packed Single-precision
    addps   xmm0, xmm1                      # add Packed Single-precision
    movups  XMMWORD PTR [rdi-16], xmm0      # store back to the array
    cmp     rax, rdi
    jne     .L9                           # }while(p != endp)

当multiplier = 2.0f时,使用addps来进行加倍,会使Haswell / Broadwell的吞吐量减少一半!因为在SKL之前,FP加法只在一个执行端口上运行,但有两个FMA单元可以运行乘法。 SKL取消了加法器,并使用与mul和FMA相同的每个时钟吞吐量和延迟来运行add。(http://agner.org/optimize/,请参见the x86 tag wiki中的其他性能链接。)

使用-march=haswell编译可以让编译器为scale + add使用单个FMA。(但是,除非使用-ffast-math,否则clang不会将表达式缩减为FMA。我IRC还有一个选项可以启用FP缩减而不进行其他激进操作。)


编译器还在其他不属于传统(循环)自动向量化范畴的地方使用SSE。即使在-O1-Os下,clang和gcc也会在结构复制等情况下使用SSE/AVX指令。 - BeeOnRope
后续指令集(SSE4、AVX、AVX2、AVX512以及像BMI2、popcnt等非SIMD扩展)需要手动启用,告诉编译器可以生成在旧CPU上无法运行的代码。您所说的“手动启用”是指我们必须传递所需的编译器标志吗?还是有一些系统级别的设置需要启用这些功能?我认为应该是前者。 - Nawaz
1
@Nawaz:我是指编译器标志,以便启用代码生成使用它们。所有主流内核都支持SSE、AVX和AVX512,并通过适当设置CPU控制寄存器位来默认启用它们。 - Peter Cordes
@PeterCordes 谢谢您提供如此有见地的答案。 - picchiolu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接