编译为C ++与C时,GCC代码生成的巨大差异
我一直在玩x86-64程序集,尝试了解更多可用的各种SIMD扩展(MMX,SSE,AVX)。
为了看看不同的C或C ++构造如何通过GCC转换为机器代码,我一直在使用Compiler Explorer ,它是一个非常出色的工具。
在我的一个“游戏会话”中,我想看看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
但是,当我testing了如果使用GCC C编译器通过添加标志-xc
编译相同的代码会产生什么,我真的很惊讶。
我期望类似的,如果不是相同的结果,但C编译器似乎产生更复杂,也可能更慢的机器代码。 由此产生的组件太大,无法完全粘贴,但可以通过以下链接在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 ++都有效的代码方面更好吗?
额外的代码用于处理未alignment,因为使用的指令vmovdqa64
要求64字节alignment。
我的testing表明,即使标准没有,gcc确实允许在另一个模块中的定义覆盖在C模式下的一个。 该定义可能只符合基本的alignment要求(4字节),因此编译器不能依赖更大的alignment。 从技术上讲,gcc为这个暂时的定义发出一个.comm
程序集指令,而外部定义在.data
段中使用一个普通的符号。 在链接期间,这个符号优先于.comm
。
注意如果你改变程序使用extern unsigned int buffer[2048];
那么即使是C ++版本也会有附加的代码。 相反,使它成为static unsigned int buffer[2048];
将把C版本变成最优化版本。