I have a simple struct with an array:
struct A
{
uint32_t arr[size];
};
I have two functions, which create it using default initialization and value initialization:
template<class T>
void testDefault()
{
T* pa = new T; // Default
use(*pa);
delete pa;
}
template<class T>
void testValue()
{
T* pa = new T(); // Value
use(*pa);
delete pa;
}
I'm facing different performance for those functions. The funny thing is that performance differences vary depending on how I declare default constructor of the struct. I have three ways:
struct A
{
uint32_t arr[size];
// Implicit constructor
};
struct B
{
uint32_t arr[size];
B() {}; // Empty constructor
};
struct C
{
uint32_t arr[size];
C() = default; // Defaulted constructor
};
I thought they are all the same from compiler's point of view. Never have been I so wrong. I did run both testDefault()
and testValue()
several times with structs A
, B
and C
and measured performance. Here is what I have:
Default initialization (implict constructor) done in 880ms
Value initialization (implict constructor) done in 1145ms
Default initialization (empty constructor) done in 867ms
Value initialization (empty constructor) done in 865ms
Default initialization (defaulted constructor) done in 872ms
Value initialization (defaulted constructor) done in 1148ms
Note how performance is clearly worse for both implicit and defaulted constructors. Only empty constructor correctly shows the same performance for both different initialization forms.
I tested this with VC++, gcc and clang. See online demo for gcc. Timings are quite persistent.
What I assume is:
Since all the compilers exhibit the same timings, it seems like I'm missing something. Can anyone please explain me these timings?
(See also my question Why compilers put zeros into arrays while they do not have to? on the same topic. I give some links to cppreference there.)
Let's look at the definition of value-initialize:
To value-initialize an object of type T means:
- if T is a (possibly cv-qualified) class type with either no default constructor (12.1) or a default constructor that is user-provided or deleted, then the object is default-initialized;
- if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized [...];
- if T is an array type, then each element is value-initialized;
- otherwise, the object is zero-initialized.
Also let's review the adjectives in use here for constructors:
= default
or = delete
= default;
or implicitly generated as suchLooking at your classes:
A
has a default constructor which is implicitly declared as defaulted, and not user-provided.C
has a default constructor which is user-declared as defaulted, and not user-provided.So the second bullet point in the definition of value-initialize applies. The object is zero-initialized, meaning the arr
is zeroed out.
B
has a default constructor which is user-provided.So the first bullet point of value-initialize applies to B
; value-initialization is the same as default-initialization here, and arr
is not zeroed out.
Your timings correctly seem to correspond to what is expected: value-initialization of A
or C
zeroes out arr
and the other cases don't.
One of the constructors behaves differently under value initialization. In this version,
B() {};
the array B::arr
is not value-initialized when the B
is. With the others, it is. Whether this explains the performance difference is another matter.
So, B::arr
doesn't get zero-initialized with value initialization, whereas A::arr
and C::arr
do. All three cases have the same behaviour under default initialization, that is to say, arr
gets default-initialized, i.e. no initialization is performed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With