Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Different performance for default initialized and value initialized struct

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:

  1. Default and value initializations for UDT are the same thing
  2. All demonstrated ways of defining default constructor are doing the same thing
  3. Default constructor of these structs should leave content of the array in indeterminate state

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.)

like image 624
Mikhail Avatar asked Mar 12 '23 19:03

Mikhail


2 Answers

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:

  • user-declared - you declared the constructor
  • user-provided - you declared the constructor and didn't set it to = default or = delete
  • default - can be called with no arguments
  • declared as defaulted - marked = default; or implicitly generated as such

Looking 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.

like image 91
M.M Avatar answered Mar 16 '23 00:03

M.M


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.

like image 24
juanchopanza Avatar answered Mar 15 '23 22:03

juanchopanza