编译为C++与C时,GCC代码生成的巨大差异

Jon*_*anE 35 c c++ assembly gcc x86-64

我一直在玩x86-64程序集试图了解更多有关可用的各种SIMD扩展(MMX,SSE,AVX).

为了了解GCC如何将不同的C或C++构造转换为机器代码,我一直在使用Compiler Explorer,这是一个极好的工具.

在我的一个"游戏会话"期间,我想看看GCC如何优化整数数组的简单运行时初始化.在这种情况下,我试图将数字0到2047写入2048个无符号整数的数组.

代码如下:

unsigned int buffer[2048];

void setup()
{
  for (unsigned int i = 0; i < 2048; ++i)
  {
    buffer[i] = i;
  }
}
Run Code Online (Sandbox Code Playgroud)

如果我启用优化和AVX-512指令,-O3 -mavx512f -mtune=intelGCC 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
Run Code Online (Sandbox Code Playgroud)

但是,当我测试通过添加标志使用GCC C编译器编译相同代码时将生成的内容-x c我真的很惊讶.

我希望类似的,如果不相同,结果却在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]
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,此代码具有许多复杂的移动和跳转,并且通常感觉像执行简单数组初始化的非常复杂的方式.

为什么生成的代码有这么大的差异?

与C编译器相比,GCC C++ - 编译器在优化C和C++中有效的代码方面是否更好?

Jes*_*ter 39

额外的代码用于处理未对齐,因为使用的指令vmovdqa64需要64字节对齐.

我的测试显示,即使标准没有,gcc确实允许另一个模块中的定义在C模式下覆盖此处的定义.该定义可能只符合基本对齐要求(4个字节),因此编译器不能依赖更大的对齐.从技术上讲,gcc .comm为此暂定定义发出汇编指令,而外部定义在该.data部分中使用正常符号.在链接期间,此符号优先于该符号.comm.

请注意,如果您更改要使用的程序,extern unsigned int buffer[2048];那么即使C++版本也会添加代码.相反,制作它static unsigned int buffer[2048];会将C版本变为优化版本.

  • 注意,为了用gcc编译C代码版本,可以添加`-fno-common`编译器标志,或用`__attribute __((aligned(64)))`注释`buffer`变量,它将生成类似的代码到C++版本. (2认同)
  • @Jester暂定的定义只能在同一个翻译单元中重新定义.暂定的定义是一种定义,而不是任何情况下的"仅仅是声明".标准C中你的观点仍然是错误的. (2认同)
  • 我看的时间越长,你看起来就越正确.但是,有意或无意,但是gcc允许暂定定义被来自不同模块的初始化程序覆盖,并且将自己与它对齐.鉴于它是未定义的行为,因此可能会随着更高版本的变化而改变,但对于问题中的版本,情况似乎就是如此. (2认同)
  • 允许在多个翻译单元中没有初始化程序的定义是Unix扩展(这是一个奇怪的概念,因为在ANSI出现之前Unix已经有近20年的C),我相信它仍然在GNU程序中使用.我发现GCC实际上不太可能放弃对它的支持,即使ANSI标准在技术上允许它. (2认同)