8

In profiling my program I realized that 10% of the code is spent in a stupid std::complex<double>() constructor, using new std::complex<double>[size_of_array].

I have searched through the web and the default constructor for std::complex seems to take as real and imaginary parts the values double(). Since C++ does not initialize double numbers, I wonder why g++ bothers to initialize std::complex with zeros, and whether I could work around this through the whole program in some way (*)

(*) right now I have to special case the functions that create arrays of complex numbers to allocate uninitialized arrays of doubles and recast them as complex.

Edit: as pointed below, it was an oversight on my side. The default constructor has empty constructors for the real and imaginary part (http://en.cppreference.com/w/cpp/numeric/complex/complex)

 complex( const T& re = T(), const T& im = T() );

but the specification then introduces special cases for double

 complex(double re = 0.0, double im = 0.0);

It is this special case that introduces all the overhead, as it bypasses the actual default constructor of 'double' which does nothing (same as for int, long, float, etc).

Juanjo
  • 659
  • 3
  • 9
  • 1
    Read e.g. [this `std::complex` constructor reference](http://en.cppreference.com/w/cpp/numeric/complex/complex). The values are specified to always be initialized by using default arguments. – Some programmer dude Dec 04 '14 at 09:52
  • 1
    @Juanjo following Goz's advice is a much simpler/better alternative, but if you absolutely have to do it with arrays, then you might have to do it with _placement `new`_; not really a wise option. – legends2k Dec 04 '14 at 09:59
  • @JoachimPileborg thx for the pointer, I realized I only had read the first line complex( const T& re = T(), const T& im = T() ); and assumed that double() is therefore uninitialized. I feel the special cases below are kind of inconsistent but will have to live with it. It is a pity, because this overhead permeates all my libraries, including variable definitions and the like. Will have to work through them one by one :-/ – Juanjo Dec 04 '14 at 10:17
  • 4
    `double()` is *not* uninitialized. – T.C. Dec 04 '14 at 14:44
  • 2
    Am I missing something? The specialization exists merely to allow doubles to be passed by value - it has otherwise the same semantics as the non-specialized constructor - `double()` is equal to `0.0`. – Toby Speight Dec 05 '16 at 18:18
  • The zero-initialization of `std::complex` is very stupid. It comes from the irrational aversion of the committee towards partially-formed objects. To this day they keep tripping on endless problems from this. At worst I think this constructor should be disabled for "fast-math" compilation. – alfC Feb 10 '23 at 08:14
  • The performance cost of zero-initialization is also unacceptable in GPU code, which may need to launch a kernel to initialize objects to zero, just to override them in the next kernel launch. The way I handle this is, in my own container, to overrule the condition to is_trivially_default_constructible of `std::complex` if `T` is also trivial. I do this with an extra layer of type traits. Since I cannot solve this problem for everyone, at least I do it for my container gitlab.com/correaa/boost-multi/-/jobs/3748241734. – alfC Feb 10 '23 at 08:24

3 Answers3

5

I wonder why g++ bothers to initialize std::complex with zeros

Because the standard says it must do so, the default constructor is declared as:

constexpr complex(double re = 0.0, double im = 0.0);

so it sets both the members to zero.

It is normal for the standard library to safely initialize types, rather than leaving them uninitialized as you get with built-in types such as double and int*, for instance std::vector<double> zero-initializes its elements too if you resize it so that new elements get added. You can control this for vector by not adding elements to the vector until you know what values you want them to have.

One possible workaround for complex is to use a type that doesn't do the initialization:

struct D
{
  D() noexcept { }; // does not initialize val!
  D(double d) noexcept : val(d) { }
  operator double() const noexcept { return val; }
  D& operator=(double d) noexcept { val = d; return *this; }
  double val;
};

Now if you use std::complex<D> the default constructor does nothing. Add explicit to the converting constructor and/or the conversion operator to suit your taste.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • 1
    Ok, but this is quite absurd, because 'double' and 'int' are built-in and std::complex is nothing but an extension of them. The performance hit cannot be neglected, specially in computationally intensive applications which, I assure you, always take care of initializing their variables if they need to. – Juanjo Dec 04 '14 at 10:02
  • No, it's not an extension. Being part of the language's grammar itself (built-ins) is different from types created using the language (user-defined types). From the language's viewpoint it is just a `class`, no assumptions could be made beyond that. Its behaviour is up to the designer of the type. – legends2k Dec 04 '14 at 10:05
  • @Juanjo, I assure you that everyone claims to always initialize variables if they need to, yet somehow bugs still happen ;-) In this case the standard opted for safety over performance. That will be the right decision for some applications and wrong for some others. I'll update my answer with a workaround. – Jonathan Wakely Dec 04 '14 at 10:07
  • 1
    @JonathanWakely having a complex number initialized to zero is not better than having it not initialized at all. Same would apply to all real numbers in the C++ specification, but I will stop discussing this because it turns into a matter of opinion and what I was looking for is a workaround and there seems to be none that overrides the behavior of complex's default constructor. Also, defining my own complex numbers is not an option, because other libraries use std::complex -- I tried this and it is a mess to handle all the implicit conversions, plus all the functions (sin, cos, abs), etc. :-/ – Juanjo Dec 04 '14 at 10:21
  • The proposed workaround is incorrect, even ignoring [complex.numbers]/2 "The effect of instantiating the template `complex` for any type other than `float`, `double`, or `long double` is unspecified." The default constructor of `complex` initializes the real and imaginary parts from *value-initialized* temporary `T`s, and value-initialization of `D` will result in `D::val` being zero-initialized. (i.e., [This program never fails the assert](http://coliru.stacked-crooked.com/a/2b12a213f631376e).) – Casey Dec 04 '14 at 13:23
  • 2
    @Casey No, the default constructor of `D` is user-provided, so it will not be zero-initialized. [Your assertion will fire on clang](http://coliru.stacked-crooked.com/a/d4c0f40190c00490). – T.C. Dec 04 '14 at 14:46
  • @T.C. I stand corrected, thank you. So the only issue is non-portability due to reliance on what is technically unspecified behavior, although I would be surprised in practice to find an implementation that did not behave as expected. – Casey Dec 04 '14 at 14:58
  • 2
    Using defaults (like zeros) is LESS safe than leaving them un-initialized. Best would be to initialize to random garbage in DEBUG mode. The danger is that a gratuitous zero will mask a bug - until it doesn't. – Jive Dadson Dec 03 '16 at 16:12
  • 1
    A better (but still bad) alternative would be to initialize the doubles to NAN (not-a-number). The developers or maintenance crew would have a better chance of catching un-init errors before product release. – Jive Dadson Dec 03 '16 at 18:11
  • @JiveDadson: Actually, initialization to a signalling NaN would be far superior. And any placeholder is better than "random". – Ben Voigt Dec 04 '16 at 01:32
  • 1
    Initializing to zero (or anything) actually hides bugs that could be detected by memory checkers. The zero initialization of std::complex is a disaster. – alfC Feb 10 '23 at 08:05
4

There is an easy way of doing it. If you "reserve" the memory with a std::vector its much faster because it doesn't call the constructor on each element.

ie this:

std::vector< std::complex< double > > vec;
vec.reserve( 256 );
for( int i = 0; i < 256; i++ )
{
    vec.push_back( std::complex< double >( 1, 1 ) );
}

will be significantly faster than this:

std::complex< double >* arr = new std::complex< double >[256];
for( int i = 0; i < 256; i++ )
{
    arr[i]( std::complex< double >( 1, 1 ) );
}
delete[] arr;

because the constructor is only called once in the first example.

It has the added advantage that you have RAII on your side and 'vec' will automatically be released when its out of scope.

Goz
  • 61,365
  • 24
  • 124
  • 204
  • 2
    I am building my own numerical structures (https://github.com/juanjosegarciaripoll/tensor) and I would rather not build them on top of std::vector, which has its own overheads which become significant when doing a lot of matrix multiplications and the like. What I would like is to replicate 'vector's behavior w.r.t. uninitialized memory, now that I see the C++ committee did a stupid mistake by overspecifying std::complex. – Juanjo Dec 04 '14 at 10:09
  • @Juanjo: malloc and free won't call the constructor but its not really a C++ solution! – Goz Dec 04 '14 at 10:12
  • @legends2k I said 'stupid' because it is inconsistent with the behavior of all other real numbers, which are uninitialized, and because it therefore introduces an overhead that is unavoidable and useless. I am not sure placement new is ok, because it still calls the constructor http://www.parashift.com/c++-faq/placement-new.html – Juanjo Dec 04 '14 at 10:20
  • 1
    @Juanjo, the point of using placement new is that you don't construct the object until you know what values you want to initialize it with, so it doesn't do the unnecessary zero-init up-front. – Jonathan Wakely Dec 04 '14 at 10:21
  • @Juanjo: You could always derive your own class from std::complex that does nothing in the constructor ... – Goz Dec 04 '14 at 10:49
  • I am still unsure about how well this plays with other functions that expect std::complex. There would be a lot of implicit conversions going on everywhere, which seems like a greater potential to confuse the compiler. In any case, I will eventually give it a try. Thx, @Goz ! – Juanjo Dec 04 '14 at 13:59
  • 4
    @Goz How? The derived (from `std::complex`) has to construct its base, so you cannot avoid calling some constructor of `std::complex`. – Walter Dec 03 '16 at 16:17
0

The following is from code I am developing.

Added later: As M.M. noted in the comments, the behavior is technically undefined. Now I see that if some sleazy weasel were to change the implementation of std::complex so that it could not be trivially constructed / destructed, this would come a cropper. See also the example at http://en.cppreference.com/w/cpp/types/aligned_storage.

#include <complex>
#include <type_traits>

typedef std::complex<double> complex;

// Static array of complex that does not initialize
// with zeros (or anything).

template<unsigned N>
struct carray {
    typedef
      std::aligned_storage<sizeof(complex), alignof(complex)>::type raw;

    raw val[N];

    complex& operator[] (unsigned idx) {
        return reinterpret_cast<complex&> (val[idx]);
    }

    complex* begin() { return &operator[](0); }
    complex* end() { return &operator[](N); }
};
Jive Dadson
  • 16,680
  • 9
  • 52
  • 65
  • This is undefined behaviour: you are accessing storage with no objects in it, as if it had objects. See http://stackoverflow.com/questions/40873520/ . Even the case of PODs on that thread does not apply here, since `std::complex` has non-trivial initialization. – M.M Dec 03 '16 at 23:23
  • @M.M. I cannot imagine how this would fail. I am not about to dig through that thicket of lawyerese. If you say it's undefined, I believe. So, how to do it? Is std::complex useless to a language pedant if all those gratuitous zeros are deal-breakers? What's a mother to do? Post an answer, please. – Jive Dadson Dec 04 '16 at 00:13
  • @M.M I should have asked, is std::complex useless if the zeros cannot be tolerated.? That specialization for type double is a royal PITA. – Jive Dadson Dec 04 '16 at 00:33
  • This is bad because it assumes that `sizeof(aligned_storage) == sizeof (complex)` which is not guaranteed – Ben Voigt Dec 04 '16 at 01:35
  • If you can arrange the code to use placement-new for writing, and make sure you never read before at least one write, that would be one approach... (it's OK to placement-new multiple times, since complex has no destructor side-effects) otherwise you might have to roll your own complex number class to avoid the zero-initialization. I agree with sentiments expressed elsewhere that `complex`'s default constructor goes against the usual C++ principle of "only pay for what you use". I could turn this comment into an answer later – M.M Dec 04 '16 at 02:00
  • @M.M It also goes against the principle of "fear default values." They can easily mask nasty bugs. If I had to choose a default value for complex, it would be {NaN, NaN}. – Jive Dadson Dec 04 '16 at 03:36
  • @M.M - I was already rolling my own complex<>. It's not at all difficult. – Jive Dadson Dec 04 '16 at 03:46
  • @JiveDadson: `aligned_storage` is guaranteed to properly hold a `complex[N]`, and yes you can vivify the elements of that array on an as-needed basis. Just do the pointer arithmetic on `complex*` not on `aligned_storage*` – Ben Voigt Dec 04 '16 at 07:41
  • I did some bullet-biting and wrote a DIY complex<> type that is transparent and not at all persnickety. Goodbye std::complex, and good riddance. – Jive Dadson Dec 04 '16 at 10:54
  • std::complex is also guaranteed to be fully compatible with C99 _Complex, so yes, you can cast an (array of) std::complex to a(n array of) double[2], for example. Non-trivial initialization doesn't have much to do with that. See e.g. C++11 26.4/4. – rubenvb Dec 05 '16 at 17:20
  • ... So just use a std::array, and reinterpret it as std::complex[N]. – rubenvb Dec 05 '16 at 17:27