struct v {
int val[16];
};
struct v test(struct v a, struct v b) {
struct v res;
for (int i = 0; i < 16; i++)
res.val[i] = a.val[i] + b.val[i];
return res;
}
使用C++编译,GCC 7.2会发出以下信息:
push r10
vmovdqu32 zmm0, ZMMWORD PTR [rsp+16]
mov rax, rdi
vpaddd zmm0, zmm0, ZMMWORD PTR [rsp+80]
lea r10, [rsp+16]
vmovdqu32 ZMMWORD PTR [rdi], zmm0
pop r10
作为C语言编译:
lea r10, [rsp+8]
and rsp, -64
mov rax, rdi
push QWORD PTR [r10-8]
push rbp
mov rbp, rsp
push r10
vmovdqu32 zmm0, ZMMWORD PTR [r10]
vpaddd zmm0, zmm0, ZMMWORD PTR [r10+64]
vmovdqa64 ZMMWORD PTR [rbp-112], zmm0
vmovdqa64 xmm0, XMMWORD PTR [rbp-112]
vmovups XMMWORD PTR [rdi], xmm0
vmovdqa64 xmm0, XMMWORD PTR [rbp-96]
vmovups XMMWORD PTR [rdi+16], xmm0
vmovdqa64 xmm0, XMMWORD PTR [rbp-80]
vmovups XMMWORD PTR [rdi+32], xmm0
vmovdqa64 xmm0, XMMWORD PTR [rbp-64]
vmovups XMMWORD PTR [rdi+48], xmm0
pop r10
pop rbp
lea rsp, [r10-8]
使用GodBolt编译器探索器将C和C++进行编译:
clang x86汇编: https://godbolt.org/g/FfrKTf
gcc x86汇编: https://godbolt.org/g/SZQqqt
在gcc和clang中,相同的代码片段对于C和C++产生了显着不同的代码:
循环的自动向量化方式不同:当使用
-march=sandybridge
或比AVX2更窄的任何选项时,gcc使用非对齐加载/存储,而g++使用标量加载/存储直到对齐边界。这不是特定于x86的现象:AArch64 SIMD的自动向量化也会发生同样的情况。
C版本实际上将结果存储到堆栈上的本地
res
,然后从那里使用SIMD副本复制到返回值指针(有时与仅存储数据的循环不同的矢量宽度)。禁用自动向量化(
-fno-tree-vectorize
)后,gcc仍然使用SIMD加载/存储复制结果。
有人知道为什么会这样吗?
是否有一些在C标准中不存在但在C++标准中存在的对齐保证?这是ABI问题吗?还是编译器探索器上的一些奇怪编译选项?