Std::Array with Aggregate Initialization on G++ Generates Huge Code

std::array with aggregate initialization on g++ generates huge code

There appears to be a related bug report, Bug 59659 - large zero-initialized std::array compile time excessive. It was considered "fixed" for 4.9.0, so I consider this testcase either a regression or an edgecase not covered by the patch. For what it's worth, two of the bug report's test cases1, 2 exhibit symptoms for me on both GCC 4.9.0 as well as 5.3.1

There are two more related bug reports:

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

This is most likely a memory hog which is generating lots of default
constructors rather than a loop over them.

That one claims to be a duplicate of this one:

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

Generating the array initialization for this constexpr constructor is
the problem:

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

Indeed if we change it to S a[4096] {}; we don't get the problem.


Using perf we can see where GCC is spending most of its time. First:

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

Then 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

This won't mean much to anyone but GCC developers but it's still interesting to see what's taking up so much compilation time.


Clang 3.7.0 does a much better job at this than GCC. At -O2 it takes less than a second to compile, produces a much smaller executable (8960 bytes) and this assembly:

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]

On the other hand with GCC 5.3.1, with no optimizations, it compiles very quickly but still produces a 95328 sized executable. Compiling with -O2 reduces the executable size to 53912 but compilation time takes 4 seconds. I would definitely report this to their bugzilla.

Nested aggregate initialization of std::array

The braces in aggregate initialisation are largely optional, so you can write:

S c_arr[] = {1, 2, 3, 4};  // OK
std::array<S, 2> std_arr = {1, 2, 3, 4}; // OK

If you do add braces, though, then the braces are taken to apply to the next sub-object. Unfortunately, when you start nesting, this leads to silly code being valid, and sensible code like yours being invalid.

std::array<S, 2> std_arr = {{1, 2, 3, 4}};  // OK
std::array<S, 2> std_arr = {1, 2, {3, 4}}; // OK
std::array<S, 2> std_arr = {1, {2}, {3, 4}}; // OK

These are all okay. {1, 2, 3, 4} is a valid initialiser for the S[2] member of std_arr. {2} is okay because it is an attempt to initialise an int, and {2} is a valid initialiser for that. {3, 4} is taken as an initialiser for S, and it's also valid for that.

std::array<S, 2> std_arr = {{1, 2}, {3, 4}};  // error

This is not okay because {1, 2} is taken as a valid initialiser for the S[2] member. The remaining int sub-objects are initialised to zero.

You then have {3, 4}, but there are no more members to initialise.

As pointed out in the comments,

std::array<S, 2> std_arr = {{{1, 2}, {3, 4}}};

also works. The nested {{1, 2}, {3, 4}} is an initialiser for the S[2] member. The {1, 2} is an initialiser for the first S element. The {3, 4} is an initialiser for the second S element.

I'm assuming here that std::array<S, 2> contains an array member of type S[2], which it does on current implementations, and which I believe is likely to become guaranteed, but which has been covered on SO before and is not currently guaranteed.

Trying to understand cause of increased compile time going from C++11 to C++14

Seems like this is known bug in gcc but isn't getting much traction.

Did you try to switch over to clang++?

PS from comment: Removing the {} behind the storage{} and manually initializing works.

I have some questions about the way to assign values to the std::array

Double-braces required in C++11 prior to the CWG 1270 (not needed in C++11 after the revision and in C++14 and beyond):

// construction uses aggregate initialization
std::array<int, 5> a{ {1, 2, 3, 4, 5} }; // double-braces required in C++11 prior to the CWG 1270 revision
std::array<int, 5> a{1, 2, 3, 4, 5}; // not needed in C++11 after the revision and in C++14 and beyond
std::array<int, 5> a = {1, 2, 3, 4, 5}; // never required after =

std::array reference

Initializing a struct with aggregate initialization and member initializers

Bjarne Stroustrup and Richard Smith raised an issue about aggregate initialization and member-initializers not working together.

The definition of aggregate is slightly changed in C++11 & C++14 standard.

From the C++11 standard draft n3337 section 8.5.1 says that:

An aggregate is an array or a class (Clause 9) with no user-provided
constructors (12.1), no brace-or-equal- initializers for non-static
data members
(9.2), no private or protected non-static data members
(Clause 11), no base classes (Clause 10), and no virtual functions
(10.3).

But C++14 standard draft n3797 section 8.5.1 says that:

An aggregate is an array or a class (Clause 9) with no user-provided
constructors (12.1), no private or protected non-static data members
(Clause 11), no base classes (Clause 10), and no virtual functions
(10.3).

So, when you use in class member initializer (i.e. equal initializer) for the data member id in C++11 it no longer remains aggregate & you can't write ABC abc{"hi", 0}; to initialize a struct ABC. Because it no longer remains aggregate type after that. But your code is valid in C++14. (See live demo here).

std::array initializer list initialization in initialization list

The correct way to go about this is Array{{4, 5, 6}}. You cannot omit braces when you initialize a member with aggregate initialization. The only time you can omit braces is in a declaration of the form

T t = { ... }

So in your case you have to type out all braces: One for the std::array itself, and one for the int array. For Array3, your syntax is correct too, since int can be converted to Element2 implicitly.

From the remaining commented ones, the Array3{{{4}, {5}, {6}}}, Array3{{Element2{4}, Element2{5}, Element2{6}}} and Array3{{{Element2{4}}, {Element2{5}}, {Element2{6}}}} work too, but are more wordy. However conceptionally the Array3{{{4}, {5}, {6}}} one produces the least amount of temporaries on implementations that don't do copy elision (I guess that's irrelevant, but still good to know), even less than the Array3{{4, 5, 6}} one, because instead of copy initialization you use copy list initialization for your Element2, which doesn't produce an intermediary temporary by design.

How to construct std::array object with initializer list?

An std::array<> has no constructor that takes an std::initializer_list<> (initializer list constructor) and there is no special language support for what it may mean to pass a std::initializer_list<> to a class' constructors such that that may work. So that fails.

For it to work, your derived class needs to catch all elements and then forward them, a constructor template:

template<typename ...E>
enum_addressable_array(E&&...e) : base_t{{std::forward<E>(e)...}} {}

Note that you need {{...}} in this case because brace elision (omitting braces like in your case) does not work at that place. It's only allowed in declarations of the form T t = { ... }. Because an std::array<> consists of a struct embedding a raw array, that will need two level of braces. Unfortunately, I believe that the exact aggregate structure of std::array<> is unspecified, so you will need to hope that it works on most implementations.



Related Topics



Leave a reply



Submit