2016-05-16 1 views
20

На г ++ 4.9.2 и 5.3.1, этот код занимает несколько секунд, чтобы компилировать и производит 52776 байт исполняемый файл:станд :: массива с совокупной инициализацией на г ++ генерирует огромный код

#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.

Я все для разворачивания цикла в качестве оптимизации, но я не думаю, что это очень хорошо. Прежде чем сообщать об этом как об ошибке, может ли кто-нибудь подтвердить, является ли это ожидаемым поведением?

[edit: Я открыл a new bug для этого, потому что другие, похоже, не совпадают. Они были больше о длительном времени компиляции, чем о странном кодеге.]

+4

Ничего себе. g ++ делает это и в 6.1. Я получил компилятор для сбоя и выдаю предупреждение об ошибке отправки на godbolt: https://godbolt.org/g/Ae75GH – NathanOliver

+0

@NathanOliver Welp, это подтверждает его. Благодарю. – 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, поэтому я рассматриваю этот тест либо регрессию, либо edgecase, не охватываемую патчем. Для чего это стоит, два из тестовых случаев сообщение об ошибке в 1, 2 проявляются симптомы для меня на обоих GCC 4.9.0, а также 5.3.1

Есть две дополнительные сообщения об ошибках:

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

Andrew Pinski 2015-11-04 7:56:57 UTC

Это, скорее всего, боров памяти, которая генерирует много умолчанию конструкторы довольно чем петля над ними.

Это один утверждает, что является дубликатом этого:

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

Джонатан Wakely 2016-01-26 15: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, но это все-таки интересно посмотреть, что занимает так много времени компиляции.


Clang 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

Смежные вопросы