2016-05-16 53 views
20

在克++ 4.9.2和5.3.1,此代码需要几秒钟来编译,并产生一个52776字节的可执行:的std ::阵列与克++集合初始化产生巨大代码

#include <array> 
#include <iostream> 

int main() 
{ 
    constexpr std::size_t size = 4096; 

    struct S 
    { 
     float f; 
     S() : f(0.0f) {} 
    }; 

    std::array<S, size> a = {}; // <-- note aggregate initialization 

    for (auto& e : a) 
     std::cerr << e.f; 

    return 0; 
} 

增加size似乎线性增加编译时间和可执行文件大小。我无法用clang 3.5或Visual C++ 2015重现此行为。使用-Os没有任何区别。

$ time g++ -O2 -std=c++11 test.cpp 
real 0m4.178s 
user 0m4.060s 
sys  0m0.068s 

检查汇编代码表明的a初始化被展开,生成 movl指令:

main: 
.LFB1313: 
    .cfi_startproc 
    pushq %rbx 
    .cfi_def_cfa_offset 16 
    .cfi_offset 3, -16 
    subq $16384, %rsp 
    .cfi_def_cfa_offset 16400 
    movl $0x00000000, (%rsp) 
    movl $0x00000000, 4(%rsp) 
    movq %rsp, %rbx 
    movl $0x00000000, 8(%rsp) 
    movl $0x00000000, 12(%rsp) 
    movl $0x00000000, 16(%rsp) 
     [...skipping 4000 lines...] 
    movl $0x00000000, 16376(%rsp) 
    movl $0x00000000, 16380(%rsp) 

这只发生时T具有一个非平凡的构造和阵列被初始化使用{}。如果我执行以下任何操作,g ++会生成一个简单的循环:

  1. 删除S::S();
  2. 删除S::S()并初始化S::f课堂上;
  3. 删除聚合初始化(= {});
  4. 编译时没有-O2

我所有的循环展开作为优化,但我不认为这是一个很好的。在我将此作为错误报告之前,有人可以确认这是否是预期的行为?

[编辑:我已经为此打开a new bug,因为其他人似乎不匹配。他们更多的是编译时间长于奇怪的代码。]

+4

哇。 g ++在6.1中也是这样做的。我得到了编译器将崩溃,并就godbolt一个提交错误警告:https://godbolt.org/g/Ae75GH – NathanOliver

+0

@NathanOliver韦尔普,这有点儿证实了这一点。谢谢。 – isanae

+2

gcc处理constexpr数组也是可疑的。它在初始化constexpr std :: array时做类似的事情 = make_array(...)其中make_array()是constexpr。 –

回答

12

似乎有一个相关的错误报告,Bug 59659 - large zero-initialized std::array compile time excessive。它被认为是“固定的”4.9.0,所以我认为这个测试用例不是由补丁覆盖的回归或边缘情况。对于它的价值,两者的bug报告的测试用例12症状表现出对我来说两个GCC 4.9.0以及5.3.1

有两个相关的错误报告:

Bug 68203 - Аbout infinite compilation time on struct with nested array of pairs with -std=c++11

安德鲁·平斯基2015年11月4日7点56分57秒UTC

这是最有可能是内存猪,其产生大量违约而 建设者比对他们的循环。

那一个声称是这一个的副本:

Bug 56671 - Gcc uses large amounts of memory and processor power with large C++11 bitsets

乔纳森Wakely 2016年1月26日十五点12分27秒UTC

生成用于阵列初始化这个constexpr的构造函数是 问题:

constexpr _Base_bitset(unsigned long long __val) noexcept 
    : _M_w{ _WordT(__val) 
    } { } 

事实上,如果我们将其更改为S a[4096] {};我们没有得到这个问题。


使用perf我们可以看到GCC花费其大部分时间。第一:

perf record g++ -std=c++11 -O2 test.cpp

然后perf report

10.33% cc1plus cc1plus     [.] get_ref_base_and_extent 
    6.36% cc1plus cc1plus     [.] memrefs_conflict_p 
    6.25% cc1plus cc1plus     [.] vn_reference_lookup_2 
    6.16% cc1plus cc1plus     [.] exp_equiv_p 
    5.99% cc1plus cc1plus     [.] walk_non_aliased_vuses 
    5.02% cc1plus cc1plus     [.] find_base_term 
    4.98% cc1plus cc1plus     [.] invalidate 
    4.73% cc1plus cc1plus     [.] write_dependence_p 
    4.68% cc1plus cc1plus     [.] estimate_calls_size_and_time 
    4.11% cc1plus cc1plus     [.] ix86_find_base_term 
    3.41% cc1plus cc1plus     [.] rtx_equal_p 
    2.87% cc1plus cc1plus     [.] cse_insn 
    2.77% cc1plus cc1plus     [.] record_store 
    2.66% cc1plus cc1plus     [.] vn_reference_eq 
    2.48% cc1plus cc1plus     [.] operand_equal_p 
    1.21% cc1plus cc1plus     [.] integer_zerop 
    1.00% cc1plus cc1plus     [.] base_alias_check 

这没有多大的意义,不过是到GCC开发者,但它仍然是有趣的,看看有什么占用了这么多的编译时间。


锵3.7.0在这方面做得比GCC好得多。在-O2花费不到一秒钟进行编译,产生一个更小的可执行文件(8960个字节),并且该组件:

0000000000400810 <main>: 
    400810: 53      push rbx 
    400811: 48 81 ec 00 40 00 00 sub rsp,0x4000 
    400818: 48 8d 3c 24    lea rdi,[rsp] 
    40081c: 31 db     xor ebx,ebx 
    40081e: 31 f6     xor esi,esi 
    400820: ba 00 40 00 00   mov edx,0x4000 
    400825: e8 56 fe ff ff   call 400680 <[email protected]> 
    40082a: 66 0f 1f 44 00 00  nop WORD PTR [rax+rax*1+0x0] 
    400830: f3 0f 10 04 1c   movss xmm0,DWORD PTR [rsp+rbx*1] 
    400835: f3 0f 5a c0    cvtss2sd xmm0,xmm0 
    400839: bf 60 10 60 00   mov edi,0x601060 
    40083e: e8 9d fe ff ff   call 4006e0 <[email protected]> 
    400843: 48 83 c3 04    add rbx,0x4 
    400847: 48 81 fb 00 40 00 00 cmp rbx,0x4000 
    40084e: 75 e0     jne 400830 <main+0x20> 
    400850: 31 c0     xor eax,eax 
    400852: 48 81 c4 00 40 00 00 add rsp,0x4000 
    400859: 5b      pop rbx 
    40085a: c3      ret  
    40085b: 0f 1f 44 00 00   nop DWORD PTR [rax+rax*1+0x0] 

在另一方面与GCC 5.3.1,没有优化,它非常快速地编译,但仍然生成一个95328大小的可执行文件。使用-O2编译将可执行文件大小减小到53912,但编译时间需要4秒。我会绝对这报告给他们的bugzilla。

+1

谢谢。铿锵虽然不是那么聪明。如果我将'f'初始化为0以外的东西,它将会执行* memset'和一个循环。但它不会展开任何东西。 – isanae

+0

事实上,测试用例[一个评论](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59659#c6)这个错误报告仍然有类似症状的失败。 – isanae

+2

@isanae [This one](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59659#c2)。我认为这个问题根本不是“固定的”,因为它们在4.9.x上出现了症状。所以这可能不是回归,而是无效修复。 – user6342117