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:
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:
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:
- Eliminar
S::S()
; - Elimine
S::S()
e inicialiceS::f
en clase; - Eliminar la inicialización agregada (
= {}
); - 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.]