c++ optimization g++ stdarray loop-unrolling

c++ - std:: array con inicialización agregada en g++ genera código enorme



optimization stdarray (1)

Parece que hay un informe de error relacionado, Error 59659 - tiempo de compilación std :: array grande con cero inicial grande . Se consideró "fijo" para 4.9.0, por lo que considero que este testcase es una regresión o un edgecase no cubierto por el parche. Para lo que vale, dos de los casos de prueba 1 , 2 del informe de error presentan síntomas para mí tanto en GCC 4.9.0 como en 5.3.1

Hay dos informes de errores más relacionados:

Error 68203 - Acerca del tiempo de compilación infinito en la estructura con una matriz anidada de pares con -std = c ++ 11

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

Lo más probable es que este sea un montón de memoria que genera muchos constructores predeterminados en lugar de un bucle sobre ellos.

Ese se dice que es un duplicado de este:

Error 56671 - Gcc usa grandes cantidades de memoria y potencia de procesador con grandes conjuntos de bits C ++ 11

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

El problema es generar la inicialización de la matriz para este constructor constexpr:

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

De hecho, si lo cambiamos a S a[4096] {}; no entendemos el problema

Usando perf podemos ver donde GCC está gastando la mayor parte de su tiempo. Primero:

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

Entonces 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

Esto no significará mucho para nadie más que para los desarrolladores de GCC, pero aún es interesante ver lo que toma tanto tiempo de compilación.

Clang 3.7.0 hace un trabajo mucho mejor en esto que GCC. En -O2 se tarda menos de un segundo en compilar, produce un ejecutable mucho más pequeño (8960 bytes) y este ensamblaje:

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 <memset@plt> 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 <_ZNSo9_M_insertIdEERSoT_@plt> 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]

Por otro lado, con GCC 5.3.1, sin optimizaciones, se compila muy rápidamente pero aún produce un ejecutable de tamaño 95328. La -O2 con -O2 reduce el tamaño del ejecutable a 53912 pero el tiempo de compilación toma 4 segundos. Definitivamente informaría esto a su bugzilla.

En g ++ 4.9.2 y 5.3.1, este código tarda varios segundos en compilarse y produce un ejecutable de 52,776 bytes:

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

El aumento del size parece aumentar el tiempo de compilación y el tamaño del ejecutable de forma lineal. No puedo reproducir este comportamiento con Clang 3.5 o Visual C ++ 2015. El uso de -Os no hace ninguna diferencia.

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

La inspección del código de ensamblaje revela que la inicialización de a se desenrolla, generando 4096 instrucciones de 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)

Esto solo sucede cuando T tiene un constructor no trivial y la matriz se inicializa utilizando {} . Si hago algo de lo siguiente, g ++ genera un bucle simple:

  1. Eliminar S::S() ;
  2. Elimine S::S() e inicialice S::f en clase;
  3. Eliminar la inicialización agregada ( = {} );
  4. Compilar sin -O2 .

Estoy a favor del desenrollado de bucles como optimización, pero no creo que esta sea muy buena. Antes de informar esto como un error, ¿alguien puede confirmar si este es el comportamiento esperado?

[Editar: He abierto un nuevo error para esto porque los otros no parecen coincidir. Tenían más tiempo de compilación que de código extraño.]