public inbox for gcc-bugs@sourceware.org
help / color / mirror / Atom feed
* [Bug c++/112666] New: Missed optimization: Value initialization zero-initializes members with user-defined constructor
@ 2023-11-22  8:58 paisanafc at gmail dot com
  2023-11-22 11:40 ` [Bug c++/112666] " redi at gcc dot gnu.org
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: paisanafc at gmail dot com @ 2023-11-22  8:58 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=112666

            Bug ID: 112666
           Summary: Missed optimization: Value initialization
                    zero-initializes members with user-defined constructor
           Product: gcc
           Version: 11.4.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: paisanafc at gmail dot com
  Target Milestone: ---

Looking for the presence of "memset" instructions in the generated assembly, it
seems that gcc is zero-initializing class members with user-defined
constructors that shouldn't need to be zero-initialized.

I share below the example benchmark and a godbolt link for convenience
(https://godbolt.org/z/158q6sfen). I used the benchmark library as I didn't
know an easy way to reproduce the instruction `benchmark::DoNotOptimize`. I
hope that's ok.

---

#include <benchmark/benchmark.h>
#include <array>

struct A {
    A() = default;
    ~A() {
      benchmark::DoNotOptimize(c); // avoid inlining
    }
    std::array<char, 50000> member;
    char c;
};

struct B {
    B() {}  // user-defined ctor
    ~B() {
      benchmark::DoNotOptimize(c); // avoid inlining
    }
    std::array<char, 50000> member;
    char c;
};

struct C {
    // no user-defined ctor
    B b;
    int dummy;
};

// The benchmark code:

static void ACreation(benchmark::State& state) {
  for (auto _ : state) {
    A a{};
    benchmark::DoNotOptimize(a);
  }
}
BENCHMARK(ACreation);
static void BCreation(benchmark::State& state) {
  for (auto _ : state) {
    B b{};
    benchmark::DoNotOptimize(b);
  }
}
BENCHMARK(BCreation);
static void CCreation(benchmark::State& state) {
  for (auto _ : state) {
    C c{};
    benchmark::DoNotOptimize(c);
  }
}
BENCHMARK(CCreation);
BENCHMARK_MAIN();

---

When I run this with https://github.com/google/benchmark, I get the following
results (with gcc++11.4 and above):

-----------------------------------------------------
Benchmark           Time             CPU   Iterations
-----------------------------------------------------
ACreation         736 ns          736 ns       933741
BCreation        3.62 ns         3.62 ns    191180154
CCreation         755 ns          754 ns       944906

The struct "C" which is just "B" and an int is much slower at being initialized
than B when value initialization (via {}) is used. However, my understanding of
the C++ standard is that members with a user-defined default constructor do not
need to be zero-initialized in this situation. Looking at the godbolt assembly
output, I see that both `A a{}` and `C c{}` generate a memset instruction,
while `B b{}` doesn't. Clang, on the other hand, seems to initialize C almost
as fast as B.

This potentially missed optimization in gcc is particularly nasty for structs
with large embedded storage (e.g. structs that contain C-arrays, std::arrays,
or static_vectors).

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [Bug c++/112666] Missed optimization: Value initialization zero-initializes members with user-defined constructor
  2023-11-22  8:58 [Bug c++/112666] New: Missed optimization: Value initialization zero-initializes members with user-defined constructor paisanafc at gmail dot com
@ 2023-11-22 11:40 ` redi at gcc dot gnu.org
  2023-11-23 15:05 ` paisanafc at gmail dot com
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: redi at gcc dot gnu.org @ 2023-11-22 11:40 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=112666

--- Comment #1 from Jonathan Wakely <redi at gcc dot gnu.org> ---
(In reply to Francisco Paisana from comment #0)
> The struct "C" which is just "B" and an int is much slower at being
> initialized than B when value initialization (via {}) is used. However, my
> understanding of the C++ standard is that members with a user-defined
> default constructor do not need to be zero-initialized in this situation.

I think that's not quite right. Types with a user-provided default constructor
will not be zero-initialized when value-init is used. B does have a
user-provided default constructor, so value-init for an object of type B does
not perform zero-init first.

But that applies when constructing a complete B object, not when constructing a
member subobject.

C does not have a user-provided default constructor, so value-initialization
means:

"- the object is zero-initialized and the semantic constraints for
default-initialization are checked, and if T has a non-trivial default
constructor, the object is default-initialized;"

So first it's zero-initialized, which means:

"- if T is a (possibly cv-qualified) non-union class type, its padding bits
(6.8.1) are initialized to zero bits and each non-static data member, each
non-virtual base class subobject, and, if the object is not a base class
subobject, each virtual base class subobject is zero-initialized;"

This specifically says that *each non-static data member ... is
zero-initialized." So the B subobject must be zero-initialized. That's not the
same as when you value-init a B object.

> Looking at the godbolt assembly output, I see that both `A a{}` and `C c{}`
> generate a memset instruction, while `B b{}` doesn't. Clang, on the other
> hand, seems to initialize C almost as fast as B.

I don't know whether Clang considers the zero-init to be dead stores that are
clobbered by B() and so can be eliminated, or something else. But my
understanding of the standard is that requiring zero-init of B's members is
very intentional here.

> This potentially missed optimization in gcc is particularly nasty for
> structs with large embedded storage (e.g. structs that contain C-arrays,
> std::arrays, or static_vectors).

Arguably, the problem here is that B has a default ctor that intentionally
leaves members uninitialized. If you want to preserve that behaviour in types
that contain a B subobject, then you also need to give those types (e.g. C in
your example) a user-provided default ctor.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [Bug c++/112666] Missed optimization: Value initialization zero-initializes members with user-defined constructor
  2023-11-22  8:58 [Bug c++/112666] New: Missed optimization: Value initialization zero-initializes members with user-defined constructor paisanafc at gmail dot com
  2023-11-22 11:40 ` [Bug c++/112666] " redi at gcc dot gnu.org
@ 2023-11-23 15:05 ` paisanafc at gmail dot com
  2023-11-23 15:08 ` paisanafc at gmail dot com
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: paisanafc at gmail dot com @ 2023-11-23 15:05 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=112666

--- Comment #2 from Francisco Paisana <paisanafc at gmail dot com> ---
Jonathan Wakely, thanks a lot for your clarification. I finally got it. 

In summary, we established that:
1. if a type T (in my case C) has no user-defined ctor, it will be
zero-initialized.
2. and for that T, "each non-static data member ... is zero-initialized."

For others that might fall into the same trap as me, the important detail in 2
is that members are "zero-initialized" and not "value-initialized". If
non-static data members were value-initialized (not the case!), then my
original comment would have been true based on the clause (see
https://en.cppreference.com/w/cpp/language/zero_initialization):

"Zero-initialization is performed in the following situations:
...
2) As part of value-initialization sequence [...] for members of
value-initialized class types that have no constructors."

I wonder if there is a way to forbid the members of a class type from ever
being zero-initialized in C++.

In any case, we can mark this issue as solved.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [Bug c++/112666] Missed optimization: Value initialization zero-initializes members with user-defined constructor
  2023-11-22  8:58 [Bug c++/112666] New: Missed optimization: Value initialization zero-initializes members with user-defined constructor paisanafc at gmail dot com
  2023-11-22 11:40 ` [Bug c++/112666] " redi at gcc dot gnu.org
  2023-11-23 15:05 ` paisanafc at gmail dot com
@ 2023-11-23 15:08 ` paisanafc at gmail dot com
  2023-11-23 15:37 ` paisanafc at gmail dot com
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: paisanafc at gmail dot com @ 2023-11-23 15:08 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=112666

Francisco Paisana <paisanafc at gmail dot com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
         Resolution|---                         |FIXED
             Status|UNCONFIRMED                 |RESOLVED

--- Comment #3 from Francisco Paisana <paisanafc at gmail dot com> ---
This ended up being a misinterpretation of the C++ standard.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [Bug c++/112666] Missed optimization: Value initialization zero-initializes members with user-defined constructor
  2023-11-22  8:58 [Bug c++/112666] New: Missed optimization: Value initialization zero-initializes members with user-defined constructor paisanafc at gmail dot com
                   ` (2 preceding siblings ...)
  2023-11-23 15:08 ` paisanafc at gmail dot com
@ 2023-11-23 15:37 ` paisanafc at gmail dot com
  2023-11-23 16:34 ` redi at gcc dot gnu.org
  2023-11-23 16:35 ` sjames at gcc dot gnu.org
  5 siblings, 0 replies; 7+ messages in thread
From: paisanafc at gmail dot com @ 2023-11-23 15:37 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=112666

--- Comment #4 from Francisco Paisana <paisanafc at gmail dot com> ---
One last thing, I might have misread this as well. 

> "Zero-initialization is performed in the following situations:
> ...
> 2) As part of value-initialization sequence [...] for members of
> value-initialized class types that have no constructors."

I was interpreting it as "members that have no ctors of classes are zero-init".
However, this could be also read as "members of classes, where the classes have
no ctor, are zero-init."

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [Bug c++/112666] Missed optimization: Value initialization zero-initializes members with user-defined constructor
  2023-11-22  8:58 [Bug c++/112666] New: Missed optimization: Value initialization zero-initializes members with user-defined constructor paisanafc at gmail dot com
                   ` (3 preceding siblings ...)
  2023-11-23 15:37 ` paisanafc at gmail dot com
@ 2023-11-23 16:34 ` redi at gcc dot gnu.org
  2023-11-23 16:35 ` sjames at gcc dot gnu.org
  5 siblings, 0 replies; 7+ messages in thread
From: redi at gcc dot gnu.org @ 2023-11-23 16:34 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=112666

--- Comment #5 from Jonathan Wakely <redi at gcc dot gnu.org> ---
(In reply to Francisco Paisana from comment #4)
> One last thing, I might have misread this as well. 
> 
> > "Zero-initialization is performed in the following situations:
> > ...
> > 2) As part of value-initialization sequence [...] for members of
> > value-initialized class types that have no constructors."
> 
> I was interpreting it as "members that have no ctors of classes are
> zero-init".

I don't even know what that would mean.

> However, this could be also read as "members of classes, where
> the classes have no ctor, are zero-init."

This is the correct reading.

N.B. cppreference is not the standard. It's usually an accurate paraphrasing of
the standard, but it's not gospel.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [Bug c++/112666] Missed optimization: Value initialization zero-initializes members with user-defined constructor
  2023-11-22  8:58 [Bug c++/112666] New: Missed optimization: Value initialization zero-initializes members with user-defined constructor paisanafc at gmail dot com
                   ` (4 preceding siblings ...)
  2023-11-23 16:34 ` redi at gcc dot gnu.org
@ 2023-11-23 16:35 ` sjames at gcc dot gnu.org
  5 siblings, 0 replies; 7+ messages in thread
From: sjames at gcc dot gnu.org @ 2023-11-23 16:35 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=112666

Sam James <sjames at gcc dot gnu.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
         Resolution|FIXED                       |INVALID

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2023-11-23 16:35 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-11-22  8:58 [Bug c++/112666] New: Missed optimization: Value initialization zero-initializes members with user-defined constructor paisanafc at gmail dot com
2023-11-22 11:40 ` [Bug c++/112666] " redi at gcc dot gnu.org
2023-11-23 15:05 ` paisanafc at gmail dot com
2023-11-23 15:08 ` paisanafc at gmail dot com
2023-11-23 15:37 ` paisanafc at gmail dot com
2023-11-23 16:34 ` redi at gcc dot gnu.org
2023-11-23 16:35 ` sjames at gcc dot gnu.org

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).