编译为 C++ 与 C 时,GCC 代码生成的巨大差异
Big differences in GCC code generation when compiling as C++ vs C
我一直在玩 x86-64 汇编,试图了解更多可用的各种 SIMD 扩展(MMX、SSE、AVX)。
为了查看 GCC 如何将不同的 C 或 C++ 结构转换为机器代码,我一直在使用 Compiler Explorer,这是一个极好的工具。
在我的一个 'play sessions' 期间,我想看看 GCC 如何优化整数数组的简单 运行 时间初始化。在这种情况下,我尝试将数字 0 到 2047 写入 2048 个无符号整数数组。
代码如下所示:
unsigned int buffer[2048];
void setup()
{
for (unsigned int i = 0; i < 2048; ++i)
{
buffer[i] = i;
}
}
如果我启用优化和 AVX-512 指令 -O3 -mavx512f -mtune=intel
GCC 6.3 会生成一些非常聪明的代码:)
setup():
mov eax, OFFSET FLAT:buffer
mov edx, OFFSET FLAT:buffer+8192
vmovdqa64 zmm0, ZMMWORD PTR .LC0[rip]
vmovdqa64 zmm1, ZMMWORD PTR .LC1[rip]
.L2:
vmovdqa64 ZMMWORD PTR [rax], zmm0
add rax, 64
cmp rdx, rax
vpaddd zmm0, zmm0, zmm1
jne .L2
ret
buffer:
.zero 8192
.LC0:
.long 0
.long 1
.long 2
.long 3
.long 4
.long 5
.long 6
.long 7
.long 8
.long 9
.long 10
.long 11
.long 12
.long 13
.long 14
.long 15
.LC1:
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
但是,当我测试如果使用 GCC C 编译器通过添加标志 -x c
编译相同的代码会生成什么时,我真的很惊讶。
我期望类似的结果,如果不完全相同,但 C 编译器似乎生成 多 更复杂并且可能也慢得多的机器代码。生成的程序集太大,无法完整粘贴到此处,但可以通过以下 this link.
在 godbolt.org 查看
生成的代码片段,第 58 行到第 83 行,如下所示:
.L2:
vpbroadcastd zmm0, r8d
lea rsi, buffer[0+rcx*4]
vmovdqa64 zmm1, ZMMWORD PTR .LC1[rip]
vpaddd zmm0, zmm0, ZMMWORD PTR .LC0[rip]
xor ecx, ecx
.L4:
add ecx, 1
add rsi, 64
vmovdqa64 ZMMWORD PTR [rsi-64], zmm0
cmp ecx, edi
vpaddd zmm0, zmm0, zmm1
jb .L4
sub edx, r10d
cmp r9d, r10d
lea eax, [r8+r10]
je .L1
mov ecx, eax
cmp edx, 1
mov DWORD PTR buffer[0+rcx*4], eax
lea ecx, [rax+1]
je .L1
mov esi, ecx
cmp edx, 2
mov DWORD PTR buffer[0+rsi*4], ecx
lea ecx, [rax+2]
如您所见,这段代码有很多复杂的移动和跳跃,总的来说感觉像是执行简单数组初始化的一种非常复杂的方法。
为什么生成的代码差别这么大?
与 C 编译器相比,GCC C++ 编译器在优化在 C 和 C++ 中均有效的代码方面是否总体上更好?
额外的代码用于处理错位,因为使用的指令 vmovdqa64
需要 64 字节对齐。
我的测试表明,即使标准没有,gcc 也允许另一个模块中的定义在 C 模式下覆盖此处的定义。该定义可能仅符合基本对齐要求(4 字节),因此编译器不能依赖更大的对齐。从技术上讲,gcc 为这个暂定定义发出一个 .comm
汇编指令,而外部定义在 .data
部分使用普通符号。在链接期间,此符号优先于 .comm
符号。
请注意,如果您将程序更改为使用 extern unsigned int buffer[2048];
,那么即使是 C++ 版本也会添加代码。反之,设为 static unsigned int buffer[2048];
会将 C 版本变为优化版本。
我一直在玩 x86-64 汇编,试图了解更多可用的各种 SIMD 扩展(MMX、SSE、AVX)。
为了查看 GCC 如何将不同的 C 或 C++ 结构转换为机器代码,我一直在使用 Compiler Explorer,这是一个极好的工具。
在我的一个 'play sessions' 期间,我想看看 GCC 如何优化整数数组的简单 运行 时间初始化。在这种情况下,我尝试将数字 0 到 2047 写入 2048 个无符号整数数组。
代码如下所示:
unsigned int buffer[2048];
void setup()
{
for (unsigned int i = 0; i < 2048; ++i)
{
buffer[i] = i;
}
}
如果我启用优化和 AVX-512 指令 -O3 -mavx512f -mtune=intel
GCC 6.3 会生成一些非常聪明的代码:)
setup():
mov eax, OFFSET FLAT:buffer
mov edx, OFFSET FLAT:buffer+8192
vmovdqa64 zmm0, ZMMWORD PTR .LC0[rip]
vmovdqa64 zmm1, ZMMWORD PTR .LC1[rip]
.L2:
vmovdqa64 ZMMWORD PTR [rax], zmm0
add rax, 64
cmp rdx, rax
vpaddd zmm0, zmm0, zmm1
jne .L2
ret
buffer:
.zero 8192
.LC0:
.long 0
.long 1
.long 2
.long 3
.long 4
.long 5
.long 6
.long 7
.long 8
.long 9
.long 10
.long 11
.long 12
.long 13
.long 14
.long 15
.LC1:
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
但是,当我测试如果使用 GCC C 编译器通过添加标志 -x c
编译相同的代码会生成什么时,我真的很惊讶。
我期望类似的结果,如果不完全相同,但 C 编译器似乎生成 多 更复杂并且可能也慢得多的机器代码。生成的程序集太大,无法完整粘贴到此处,但可以通过以下 this link.
在 godbolt.org 查看生成的代码片段,第 58 行到第 83 行,如下所示:
.L2:
vpbroadcastd zmm0, r8d
lea rsi, buffer[0+rcx*4]
vmovdqa64 zmm1, ZMMWORD PTR .LC1[rip]
vpaddd zmm0, zmm0, ZMMWORD PTR .LC0[rip]
xor ecx, ecx
.L4:
add ecx, 1
add rsi, 64
vmovdqa64 ZMMWORD PTR [rsi-64], zmm0
cmp ecx, edi
vpaddd zmm0, zmm0, zmm1
jb .L4
sub edx, r10d
cmp r9d, r10d
lea eax, [r8+r10]
je .L1
mov ecx, eax
cmp edx, 1
mov DWORD PTR buffer[0+rcx*4], eax
lea ecx, [rax+1]
je .L1
mov esi, ecx
cmp edx, 2
mov DWORD PTR buffer[0+rsi*4], ecx
lea ecx, [rax+2]
如您所见,这段代码有很多复杂的移动和跳跃,总的来说感觉像是执行简单数组初始化的一种非常复杂的方法。
为什么生成的代码差别这么大?
与 C 编译器相比,GCC C++ 编译器在优化在 C 和 C++ 中均有效的代码方面是否总体上更好?
额外的代码用于处理错位,因为使用的指令 vmovdqa64
需要 64 字节对齐。
我的测试表明,即使标准没有,gcc 也允许另一个模块中的定义在 C 模式下覆盖此处的定义。该定义可能仅符合基本对齐要求(4 字节),因此编译器不能依赖更大的对齐。从技术上讲,gcc 为这个暂定定义发出一个 .comm
汇编指令,而外部定义在 .data
部分使用普通符号。在链接期间,此符号优先于 .comm
符号。
请注意,如果您将程序更改为使用 extern unsigned int buffer[2048];
,那么即使是 C++ 版本也会添加代码。反之,设为 static unsigned int buffer[2048];
会将 C 版本变为优化版本。