gpt4 book ai didi

c - 编译器是否将 SSE 指令用于常规 C 代码?

转载 作者:太空狗 更新时间:2023-10-29 14:56:55 25 4
gpt4 key购买 nike

我看到有人在使用 -msse -msse2 -mfpmath=sse默认情况下标记为希望这将提高性能。我知道当在 C 代码中使用特殊的 vector 类型时,SSE 就会参与进来。但是这些标志对常规 C 代码有什么影响吗?编译器是否使用 SSE 来优化常规 C 代码?

最佳答案

是的,如果您使用完全优化进行编译,现代编译器会使用 SSE2 自动矢量化。 clang 甚至在 -O2 时启用它,gcc 在 -O3 时启用它。

即使在 -O1 或 -Os 处,编译器也会使用 SIMD 加载/存储指令来复制或初始化结构体或比整数寄存器更宽的其他对象。这不算是自动矢量化;它更像是他们针对固定大小的小块的默认内置 memset/memcpy 策略的一部分。但它确实利用并需要支持 SIMD 指令。

SSE2 是 x86-64 的基线/非可选,因此编译器在面向 x86-64 时始终可以使用 SSE1/SSE2 指令 .以后的指令集(SSE4、AVX、AVX2、AVX512 和非 SIMD 扩展,如 BMI2、popcnt 等)必须手动启用,以告诉编译器可以生成不能在旧 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 .

为您正在编译的 CPU 调整二进制文件的最佳方法是 -O3 -march=native -mfpmath=sse ,并使用链接时间优化 + 配置文件引导优化 . (gcc -fprofile-generate/在一些测试数据上运行/gcc -fprofile-use )。

使用 -march=native如果编译器确实选择使用新指令,则生成可能无法在早期 CPU 上运行的二进制文件。配置文件引导的优化对 gcc 非常有帮助:没有它它永远不会展开循环。但是对于 PGO,它知道哪些循环经常运行/进行大量迭代,即哪些循环是“热的”并且值得花费更多的代码大小。链接时优化允许跨文件内联/常量传播。如果您的 C++ 具有许多实际上并未在头文件中定义的小函数,这将非常有用。

How to remove "noise" from GCC/clang assembly output? 有关查看编译器输出并理解它的更多信息。

下面是一些具体的例子on the Godbolt compiler explorer适用于 x86-64 . 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)

乘数 = 2.0f结果使用 addps加倍,将 Haswell/Broadwell 的吞吐量减少 2 倍!因为在 SKL 之前,FP add 只运行在一个执行端口上,但是有两个 FMA 单元可以运行乘法。 SKL 放弃了加法器,并以与 mul 和 FMA 相同的每时钟 2 吞吐量和延迟运行相加。 ( http://agner.org/optimize/ ,并在 the x86 tag wiki 中查看其他性能链接。)

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

关于c - 编译器是否将 SSE 指令用于常规 C 代码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50786263/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com