2016-05-16 34 views
20

Su g ++ 4.9.2 e 5.3.1, questo codice richiede alcuni secondi per compilare e produce un eseguibile 52.776 byte:std :: array con inizializzazione aggregato su g ++ genera enorme codice

#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; 
} 

crescente size sembra aumentare il tempo di compilazione e le dimensioni eseguibili in modo lineare. Non riesco a riprodurre questo comportamento sia con clangore 3.5 o Visual C++ 2015. Utilizzando -Os non fa differenza.

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

Controllo del codice assembly rivela che l'inizializzazione a viene srotolato, generando movl istruzioni:

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) 

questo avviene solo quando T ha un costruttore non banale e l'array è inizializzato utilizzando {}. Se eseguo una delle seguenti operazioni, g ++ genera un semplice ciclo:

  1. Rimuovi S::S();
  2. Rimuovere S::S() e inizializzare S::f in-class;
  3. Rimuovere l'inizializzazione aggregato (= {});
  4. compilare senza -O2.

Sono tutto per lo srotolamento del ciclo come ottimizzazione, ma non penso che sia molto buono. Prima di segnalare questo come un bug, qualcuno può confermare se questo è il comportamento previsto?

[edit: ho aperto a new bug per questo, perché gli altri non sembrano corrispondere. Erano più sul tempo di compilazione lungo che codegen strano.]

+4

Wow. g ++ fa anche questo in 6.1. Ho fatto arrestare il compilatore ed emettere un avviso di bug di invio su Godbolt: https://godbolt.org/g/Ae75GH – NathanOliver

+0

@NathanOliver Welp, questo lo conferma. Grazie. – isanae

+2

Anche la gestione di gC di constexpr array è sospetta. Fa una cosa simile quando si inizializza un constexpr std :: array = make_array (...) dove make_array() è constexpr. –

risposta

12

Sembra che ci sia un bug report correlato, Bug 59659 - large zero-initialized std::array compile time excessive. È stato considerato "corretto" per 4.9.0, quindi considero questo testcase una regressione o un edgecase non coperto dalla patch. Per quel che vale, due dei casi di test del rapporto bug 1, 2 presentano sintomi per me su entrambi GCC 4.9.0 e 5.3.1

Ci sono due segnalazioni di bug più correlati:

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

Andrew Pinski 2015/11/04 07:56:57 UTC

Questo è probabilmente uno spreco di memoria che sta generando un sacco di predefinito costruttori piuttosto di un cappio su di loro.

Quello sostiene di essere un duplicato di questo:

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

Jonathan Wakely 2016/01/26 15:12:27 UTC

Generare l'inizializzazione array per questo costruttore di constexpr è il problema:

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

Effettivamente se lo cambiamo in S a[4096] {}; non abbiamo il problema.


Usando perf possiamo vedere dove GCC sta spendendo la maggior parte del suo tempo. Primo:

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

Poi 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 

questo non significa molto a nessuno, ma gli sviluppatori di GCC, ma è comunque interessante per vedere cosa sta prendendo il tempo di compilazione così tanto.


Clang 3.7.0 svolge un lavoro molto migliore rispetto a GCC. Al -O2 richiede meno di un secondo per compilare, produce un eseguibile molto più piccolo (8960 byte) e questo montaggio:

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] 

D'altra parte con GCC 5.3.1, con nessuna ottimizzazione, compila molto rapidamente, ma produce ancora un eseguibile di dimensioni 95328. La compilazione con -O2 riduce la dimensione dell'eseguibile a 53912 ma il tempo di compilazione richiede 4 secondi. Vorrei sicuramente segnalarlo al loro bugzilla.

+1

Grazie. tuttavia il clang non è così intelligente. Se inizializzo 'f' su qualcosa di diverso da 0, farà * entrambi * un' memset' e un ciclo. Ma non srotolerà nulla. – isanae

+0

In effetti, un test case in [uno dei commenti] (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59659#c6) per questa segnalazione di bug continua a fallire con sintomi simili. – isanae

+2

@isanae [Questo] (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59659#c2) pure. Non penso che il problema sia stato "risolto", considerando che presentano sintomi a 4.9.x. Quindi probabilmente non è una regressione ma una correzione non valida. – user6342117