【发布时间】:2018-07-27 23:21:50
【问题描述】:
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 asm:https://godbolt.org/g/FfrKTf
gcc x86 asm:https://godbolt.org/g/SZQqqt
相同的代码 sn-p 在 gcc 和 clang 中为 C 和 C++ 生成显着不同的代码:
-
循环的自动向量化方式不同:gcc 使用未对齐的加载/存储,但 g++ 使用标量直到对齐边界,
-march=sandybridge或任何比 AVX2 更窄的东西。这不是 x86 特定的:AArch64 SIMD 的自动矢量化也会发生同样的事情。
-
C 版本实际上将结果存储到堆栈上的本地
res,然后使用 SIMD 副本从那里复制到返回值指针(有时副本的向量宽度与刚刚存储数据的循环的向量宽度不同)。在禁用自动矢量化 (
-fno-tree-vectorize) 后,gcc 仍使用 SIMD 加载/存储来复制结果。
有人知道为什么会这样吗?
是否有一些 C++ 标准中的对齐保证,但 C 标准中没有?这是 ABI 问题吗?还是编译器资源管理器上有一些奇怪的编译选项?
【问题讨论】:
-
能否复制粘贴问题中的代码和汇编程序?
-
因为 C++ 有复制省略?试试
-fno-elide-constructors。 -
C 前端与 C++ 前端的 gcc 和 clang 看起来像是错过了优化。它们必须将程序的不同内部表示形式提供给中间端,因此 C 版本会额外复制到堆栈上的本地。 ICC 使用 / 不使用
-xc进行相同的 asm。 -
我认为最终这将只是“因为它们是不同的编译器,它们只是在某些方面有所不同,[编译器]代码专注于某些方法而可能以其他方法为代价”
-
@user902384 :您一定是新来的,但 Godbolt 是一个备受推崇的领域,并且被问答作者大量使用,尤其是在 C/C++/Assembly 标签中。