Created attachment 47178 [details] preprocessed source that exhibits bad compilation performance Greetings! I stumbled upon strange compiler-performance behavior when initializing an array member of non-POD type in a struct constructor with the (C++11) brace-initialization syntax, rather than with parens. For example, if you have the following types: struct item { int i; item(); }; struct item_array { item a[SIZE]; item_array(); }; I am referring to defining the item_array constructor as: item_array::item_array() : a{} {} Instead of: item_array::item_array() : a() {} In the case with the brace initializer, especially if SIZE is non-trivial (eg, 100s of thousands), the compiler takes an inordinately long time and much memory to compile a trivial piece of code, where with the parens instead it compiles instantly and with very little memory used. I've boiled it down to a very simple program, which does not #include any headers. The (not attached) source file "good.cpp" uses the parens, and compiles instantly, where the (not attached) "bad.cpp", which differs only in that it uses brace initialization rather than parens, takes 58 seconds to compile, with the max resident set size of g++ at nearly 3GB. (This is with SIZE=512*1024) (I guess I can only attach a single file, and that is supposed to be the preprocessed "bad.ii", so that's the one attached.) If I increase the array length from 512k to 1024k (which has 4MB total size, with sizeof (int) == 4), then g++ runs furiously for some time and eventually crashes. Again, with parens instead of braces, the problem goes away. But I have no idea why this would be, as I thought the behavior would be equivalent. Is this (kind of terrible) performance/behavior expected? Thanks! Carl Bug report details: Compile with: g++ -O3 -Wall -std=c++11 bad.cpp -o bad I've also tried compiling with and without the options mentioned on the "Reporting Bugs" page (-Wall -Wextra -fno-strict-aliasing -fwrapv -fno-aggressive-loop-optimizations -fsanitize=undefined) as well as with and without optimization (-O3), none of which cause the bad compilation performance to go away. When it does compile though, it compiles without any output, even with "-Wall -Wextra". If I collect the "time" and rusage data from the above compilation step, I get: rtime: 58.421 utime: 57.607 stime: 0.798 maxrss: 2925112k Compared to compiling "good.cpp" (which only changes "a{}" to "a()"), I get: rtime: 0.076 utime: 0.047 stime: 0.007 maxrss: 23120k I've also compiled with "-save-temps" and am attaching the requested (*.i*) preprocessed file, which is "bad.ii". Version detail: $ g++ -v Using built-in specs. COLLECT_GCC=g++ COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper OFFLOAD_TARGET_NAMES=nvptx-none OFFLOAD_TARGET_DEFAULT=1 Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Ubuntu 7.4.0-1ubuntu1~18.04.1' --with-bugurl=file:///usr/share/doc/gcc-7/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with-gcc-major-version-only --program-suffix=-7 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu Thread model: posix gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)
Created attachment 47179 [details] original source file with bad compilation performance Note the brace member initialization in the constructor: "item_array() : a{} {}"
Created attachment 47181 [details] source that does not exhibit bad compilation performance Note the constructor initializes the member with parens: "item_array() : a() {}"
Original "good.cpp" and "bad.cpp" sources now attached.
It's yet another case where the C++ FE should emit loops rather than individual array element initializations. ;; Function item_array::item_array() (null) ;; enabled by -tree-original <<cleanup_point <<< Unknown tree: expr_stmt *(struct { struct item a[524288]; } &) this = {CLOBBER} >>>>>; { <<cleanup_point <<< Unknown tree: expr_stmt (void) (((struct item_array *) this)->a = {TARGET_EXPR <D.2340, <<< Unknown tree: aggr_init_expr 4 __ct_comp D.2340 (struct item *) <<< Unknown tree: void_cst >>> >>>>, TARGET_EXPR <D.2350, <<< Unknown tree: aggr_init_expr 4 __ct_comp D.2350 (struct item *) <<< Unknown tree: void_cst >>> >>>>, TARGET_EXPR <D.2351, <<< Unknown tree: aggr_init_expr ... 2 million lines later ... D.526635 (struct item *) <<< Unknown tree: void_cst >>> >>>>, TARGET_EXPR <D.526636, <<< Unknown tree: aggr_init_expr 4 __ct_comp D.526636 (struct item *) <<< Unknown tree: void_cst >>> >>>>}) >>>>>; also probably not too intelligent individual elements either.
Whereas with (): ;; Function item_array::item_array() (null) ;; enabled by -tree-original <<cleanup_point <<< Unknown tree: expr_stmt *(struct { struct item a[524288]; } &) this = {CLOBBER} >>>>>; { <<cleanup_point <<< Unknown tree: expr_stmt (void) (((struct item_array *) this)->a = <<< Unknown tree: vec_init_expr D.2341 >>>) >>>>>; } I'm not sure if () and {} are semantically equivalent [in this case].
> I'm not sure if () and {} are semantically equivalent [in this case]. For what it's worth, (not sure if I'm allowed to paste a link here, but) on cppreference.com [1] under "Constructors and member initializer lists", it describes the following forms for "member-initializers" in "the body of a function definition of any constructor" : class-or-identifier ( expression-list(optional) ) (1) class-or-identifier brace-init-list (2) (since C++11) And about these it says: 1) Initializes the base or member named by class-or-identifier using direct initialization or, if expression-list is empty, value-initialization 2) Initializes the base or member named by class-or-identifier using list-initialization (which becomes value-initialization if the list is empty and aggregate-initialization when initializing an aggregate) So it seems in this case of "a()" (1) vs "a{}" (2), either one results in "value-initialization", which is further described here [2]. The following forms are described for value-initialization: T() (1) T{} (5) (since C++11) About which it says: 1,5) when a nameless temporary object is created with the initializer consisting of an empty pair of parentheses [or braces (since C++11)]; But then it goes on to say: In all cases, if the empty pair of braces {} is used and T is an aggregate type, aggregate-initialization is performed instead of value-initialization. (And notably, an array is an aggregate type.) Then the page on aggregate-initialization[3] goes on to describe the many semantic effects of aggregate initialization... The part that seems to apply is If the number of initializer clauses is less than the number of members or initializer list is completely empty, the remaining members are initialized by empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates). (since C++11) I presume our "struct item" counts as a "non-aggregate class with default constructor", which would result in value-initialization, but my lawyer glasses are a little fuzzy here. Whereas, the way I read the value-initialization rules, if "()" is used (that is, not "the empty pair of braces {}"), then this rule applies: 3) if T is an array type, each element of the array is value-initialized; Which means that for every element of the array this applies: 1) if T is a class type with [...] a user-provided [...] constructor, the object is default-initialized; So, a bit round-about, but it seems like both routes would result in value-initialization, which ends up meaning each element gets default-initialized. But you have to squint real hard, and the rules about how to get there seem to follow surprisingly different-looking paths... Maybe that (partially) explains the different code-paths in g++. Carl [1] https://en.cppreference.com/w/cpp/language/initializer_list [2] https://en.cppreference.com/w/cpp/language/value_initialization [3] https://en.cppreference.com/w/cpp/language/aggregate_initialization
*** Bug 94957 has been marked as a duplicate of this bug. ***
*** Bug 71165 has been marked as a duplicate of this bug. ***
The master branch has been updated by Jason Merrill <jason@gcc.gnu.org>: https://gcc.gnu.org/g:e948436eab818c527dd60b0ef939c4f42fbe8ba4 commit r12-6326-ge948436eab818c527dd60b0ef939c4f42fbe8ba4 Author: Jason Merrill <jason@redhat.com> Date: Sat Jan 1 16:00:09 2022 -0500 c++: loop over array elts w/o explicit init [PR92385] The PR complains that initializing a large array with {} takes a long time to compile; this was because digest_init would turn {} into a long CONSTRUCTOR with an initializer for each element, instead of more sensibly generating a loop. The standard doesn't specify this implementation, but it does allow for it by specifying that a temporary created "when a default constructor is called to initialize an element of an array with no corresponding initializer" is destroyed "before the construction of the next array element, if any." rather than living until the end of the complete object initialization as usual. This change is also needed before the PR94041 fix extends the lifetime of temporaries from elements with explicit initializers. To implement this, I change digest_init so that in cases where initialization of trailing array elements isn't constant, we return a VEC_INIT_EXPR instead of a bare CONSTRUCTOR; when it is encountered later, we call build_vec_init to generate the actual initialization code. PR c++/92385 gcc/cp/ChangeLog: * typeck2.c (PICFLAG_VEC_INIT): New. (process_init_constructor_array): Set it. (process_init_constructor): Handle it. (split_nonconstant_init_1): Handle VEC_INIT_EXPR. * init.c (build_vec_init): Likewise. * cp-gimplify.c (cp_gimplify_expr): Factor out... * tree.c (expand_vec_init_expr): ...this function. (build_vec_init_elt): Handle BRACE_ENCLOSED_INITIALIZER_P. (build_vec_init_expr): Likewise. * constexpr.c (cxx_eval_vec_init): Likewise. (reduced_constant_expression_p): Check arrays before C++20. * cp-tree.h (expand_vec_init_expr): Declare. gcc/testsuite/ChangeLog: * g++.dg/init/array61.C: New test.
*** Bug 87680 has been marked as a duplicate of this bug. ***
*** Bug 65503 has been marked as a duplicate of this bug. ***
*** Bug 65591 has been marked as a duplicate of this bug. ***
Fixed for GCC 12.
The master branch has been updated by Jason Merrill <jason@gcc.gnu.org>: https://gcc.gnu.org/g:119cea98f664764cce04963243c39c8f6d797d33 commit r12-7069-g119cea98f664764cce04963243c39c8f6d797d33 Author: Jason Merrill <jason@redhat.com> Date: Wed Feb 2 18:36:41 2022 -0500 c++: assignment, aggregate, array [PR104300] The PR92385 fix meant that we see more VEC_INIT_EXPR outside of INIT_EXPR; in such cases, we need to wrap them in TARGET_EXPR. I previously fixed that in build_array_copy; we also need it in process_init_constructor. After fixing that, I needed to adjust a few places to recognize the VEC_INIT_EXPR even inside a TARGET_EXPR. And prevent cp_fully_fold_init from lowering VEC_INIT_EXPR too soon. And handle COMPOUND_EXPR inside TARGET_EXPR better. PR c++/104300 PR c++/92385 gcc/cp/ChangeLog: * cp-tree.h (get_vec_init_expr): New. (target_expr_needs_replace): New. * cp-gimplify.cc (cp_gimplify_init_expr): Use it. (struct cp_fold_data): New. (cp_fold_r): Only genericize inits at end of fn. (cp_fold_function): Here. (cp_fully_fold_init): Not here. * init.cc (build_vec_init): Use get_vec_init_expr. * tree.cc (build_vec_init_expr): Likewise. * typeck2.cc (split_nonconstant_init_1): Likewise. (process_init_constructor): Wrap VEC_INIT_EXPR in TARGET_EXPR. gcc/testsuite/ChangeLog: * g++.dg/cpp0x/initlist-array14.C: New test.
The master branch has been updated by Jason Merrill <jason@gcc.gnu.org>: https://gcc.gnu.org/g:4822108e61ab879067482704f2f7d1670813d61a commit r12-8066-g4822108e61ab879067482704f2f7d1670813d61a Author: Jason Merrill <jason@redhat.com> Date: Fri Apr 8 15:33:41 2022 -0400 c++: constexpr non-trivial aggregate init [PR105191] My patch for PR92385 made us use VEC_INIT_EXPR for aggregate initialization of an array where some elements are not explicitly initialized. Constexpr handling of that was treating initialization from {} as equivalent to value-initialization, which is problematic for classes with default member initializers that make the default constructor non-trivial; in older standard modes, not initializing all members makes a constructor non-constexpr, but aggregate initialization is fine. PR c++/105191 PR c++/92385 gcc/cp/ChangeLog: * tree.cc (build_vec_init_elt): Do {}-init for aggregates. * constexpr.cc (cxx_eval_vec_init): Only treat {} as value-init for non-aggregate types. (build_vec_init_expr): Also check constancy of explicit initializer elements. gcc/testsuite/ChangeLog: * g++.dg/cpp0x/constexpr-array28.C: New test.
The master branch has been updated by Jason Merrill <jason@gcc.gnu.org>: https://gcc.gnu.org/g:ce46d6041358052dfa26f3720732f0357c5d72e7 commit r13-463-gce46d6041358052dfa26f3720732f0357c5d72e7 Author: Jason Merrill <jason@redhat.com> Date: Fri May 13 16:07:10 2022 -0400 c++: array {}-init [PR105589] My patch for 105191 made us use build_value_init more frequently from build_vec_init_expr, but build_value_init doesn't like to be called to initialize a class in a template. That's caused trouble in the past, and seems like a strange restriction, so let's fix it. PR c++/105589 PR c++/105191 PR c++/92385 gcc/cp/ChangeLog: * init.cc (build_value_init): Handle class in template. gcc/testsuite/ChangeLog: * g++.dg/cpp0x/initlist-array16.C: New test.
The releases/gcc-12 branch has been updated by Jason Merrill <jason@gcc.gnu.org>: https://gcc.gnu.org/g:40f749b364b740f41ea6b211f81c21919a2e8bee commit r12-8380-g40f749b364b740f41ea6b211f81c21919a2e8bee Author: Jason Merrill <jason@redhat.com> Date: Fri May 13 16:07:10 2022 -0400 c++: array {}-init [PR105589] My patch for 105191 made us use build_value_init more frequently from build_vec_init_expr, but build_value_init doesn't like to be called to initialize a class in a template. That's caused trouble in the past, and seems like a strange restriction, so let's fix it. PR c++/105589 PR c++/105191 PR c++/92385 gcc/cp/ChangeLog: * init.cc (build_value_init): Handle class in template. gcc/testsuite/ChangeLog: * g++.dg/cpp0x/initlist-array16.C: New test.
*** Bug 109421 has been marked as a duplicate of this bug. ***