When to use std :: complex <long double> and native complex data type (struct, etc.)

I am currently working on some project that used a previous developer

std::complex<long double>

      

throughout most of the code. The software relies heavily on signal processing techniques that have been implemented using the above complex data type. Large multidimensional arrays are created, accessed, and dropped quite frequently.

The good thing about this data type is that all the required math functions (like from <cmath>

) support complex numbers, so there is little overhead for basic math operations with this data type.

Other features such as n-dimensional complex convolution of large amounts of data have been implemented by the developer in our software.

I am currently working on an extension using n-dimensional convolution A LOT. However, most of the scripts for this extension do not require complex operations . The program is quite slow at the moment and I am wondering if it would be faster to use the proprietary framework in the critical parts. Sth like

struct CPLX{
long double REAL;
long double IMAG;
}CPLX; 

      

and implement the necessary methods themselves (implement mathematical operations such as complex multiplication, phase, etc.).

For parts that don't require complex operations (which is true for most of my extension): there won't be

(a+0i)*(b+0i)

will be significantly slower than

a*b

?

Will use its own structure with efficiently implemented mathematical operations and minimal VS overhead. using std::complex

and cmath

faster?
(Apart from the fact that this will require additional testing to make sure everything is working correctly)

Is there a significant usage overhead std::complex

?
If so, when would use std::complex

be more appropriate than using your own methods and structure?

+3


source to share


5 answers


DO NOT OPEN THE WHEEL

The built-in standard libraries are already optimized and tuned for your hardware. Don't waste time doing something that is only a fraction of how good the defaults are. If you find that in a certain routine the profile shows that it is slow, use a better library, such as those offered by the Intel or GNU floating point library.



Edit: Don't be afraid of the potential library overhead of complex numbers. The only memory overhead is keeping the real and imaginary parts together in an object, and the only time overhead is actually packing them. Both of these actions will be replicated by the implementation you come across, unless you ever need complex numbers in the first place.

+8


source


For parts that don't require complex operations (which is true for most of my extension): there won't be (a + 0i) (b + 0i) significantly slower than ab

Yes it will (unless you compile with -ffast-math

, IIRC). But you can just write:



a.real() * b.real()

No need to rewrite std::complex

, it provides all the methods you need.

+3


source


Writing your own structured and manual record of operations won't do anything, but it will make your code more difficult to read and less maintainable.

What you really want is to use SSE / AVX instructions to speed things up. The best way to do it - - Use a library like Intel MKL (which has a license fee but is very fast) - Have a look at the Agner Fog vector library and its optimization guide - Learn how to write code that the compiler can easily optimize in SSE / AVX instructions.

It's also worth noting that these kinds of operations can be sped up with multithreading, which is easiest to do either with a compiler that supports auto-parallelization with the appropriate directives, or through some OpenMP (very useful to see if you've gotten around to it).

Finally, you can write your own SSE / AVX code through the built-in libraries, but this is very intensive and makes the code difficult to maintain. Also, unless you are doing some really complicated things that cannot be easily implemented with something like MKL, you probably won't get a good speed boost unless you really know what you are doing.

+1


source


As stated by Randomusername, I think not reinventing the wheel is best.

But if most scripting is not required, use a complex operation, why don't you use a wrapper over std :: complex to implement a class for both types of numbers (real and complex), an object as a real number, and another complex number using the same interface ( with little inheritance overhead, but good performance due to the difference between real - fast and complex domain with minimal changes). Better explanation in code:

template < typename T>
class number
{
   //operators declarations
   //example
   //virtual number& operator= (const T& val) = 0;
   //virtual number& operator+= (const T& val) = 0;
   //virtual number& operator-= (const T& val) = 0;
   //virtual number& operator*= (const T& val) = 0;
   //virtual number& operator/= (const T& val) = 0;
};
template < typename T>
class real : public number<T>
{
    T number;
   //operators declarations

   // number& operator= (const T& val);
   // number& operator+= (const T& val);
   // number& operator-= (const T& val);
   // number& operator*= (const T& val);
   // number& operator/= (const T& val);
};
template < typename T>
class owncomplex :public number<T>
{
    std::complex<T> _complex;
    //operators declarations

   // number& operator= (const T& val);
   // number& operator+= (const T& val);
   // number& operator-= (const T& val);
   // number& operator*= (const T& val);
   // number& operator/= (const T& val); 
};

      

It is very difficult to rewrite any operator, but you can use the std :: complex implementation and improve the real operations with literal optimization work.

+1


source


I seem to be one of the few people with the opinion that std :: complex may be too slow in some critical inner loops, but anyway, these are my two cents: Some time ago I wrote a simple piece of code that evaluates a complex polynomial in three variables and performs some divisions. I noticed that whenever I replace complex division (greater than) or multiplication (less) operators (* or /) with appropriate explicit realistic algorithms, the code is noticeably faster. After replacing most of the multiplications and divisions, I find the speed gain to be around 30-40%. This is not a fringe amount, but good enough to warrant less readability in such a limited and critical piece of code.

It was in GCC (I don't remember the version, but it was in 4.x) and I checked complex partitioning to see why it was so slow. It turned out that he performed a lot of checks for Infs and NaNs to ensure correct behavior in extreme cases of the operation. Of course, when doing numeric operations, when you get NaN, you still get lost, so this check is really unnecessary. I have not tested if this check can be disabled.

+1


source







All Articles