何时使用 std::complex<long double> 与自己的复杂数据类型(结构等)

When to use std::complex<long double> vs. own complex data type (struct etc..)

我目前正在做以前的开发人员使用过的项目

std::complex<long double>

贯穿大部分代码。该软件在很大程度上依赖于信号处理的方法,这些方法都是使用上述复杂数据类型实现的。 经常创建、访问和删除大型多维数组。

这种数据类型的好处是,所有必需的数学函数(例如 <cmath> 中的)都支持复数,因此使用这种数据类型进行基本数学运算的开销很小。

上述开发人员已在我们的软件中实现了其他功能,例如大数据量的 n 维复数卷积。

我目前正在开发大量使用 n 维卷积的扩展。但是此扩展的大多数情况 不需要复杂的操作。 该程序目前运行速度很慢,我想知道在关键部分使用专有结构是否会更快。像

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

并自己实现所需的方法(实现复数乘法、相位等数学运算...)。

对于不需要复杂操作的部分(我的大部分扩展都是如此):不会

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

慢很多

a*b

?

将使用自己的结构,实现高效的数学运算和最小的开销 VS。使用 std::complexcmath 会更快吗? (除了这需要额外的测试以确保一切正常)

使用std::complex时是否有很大的开销? 如果是这样,什么时候使用 std::complex 比使用自己的方法和结构更合适?

不要重新发明轮子

内置标准库已经针对您的硬件进行了优化和调整。不要浪费你的时间去尝试做一些只有默认设置好几分之一的东西。如果您发现在特定例程中,配置文件显示它很慢,请使用更好的库,例如 Intel 提供的库或 GNU 的复杂浮点库。

编辑: 不要害怕复数库可能带来的开销。唯一的内存开销是将实部和虚部一起存储在一个对象中,唯一的时间开销是将它们实际打包在一起。这两个操作都将由您提出的实现复制,除非您一开始就不需要复数。

制作您自己的结构并手写操作不会有任何作用,只会让您的代码更难阅读和更难维护。

您真正想要的是利用 SSE/AVX 指令来加快速度。最好的方法是 - 使用像 Intel 的 MKL 这样的库(有许可费但速度非常快) - 查看 Agner Fog 的矢量库和他的优化手册 - 研究如何编写编译器可以轻松优化为 SSE/AVX 指令

的代码

另外值得注意的是,这些类型的操作可以通过多线程加速很多,这最容易通过使用支持自动并行化的编译器和适当的指令,或者通过一些 OpenMP(a非常有用的库,知道您是否还没有使用过它)。

最后,您可以通过内部库编写自己的 SSE/AVX 代码,但这非常耗时,并且代码难以维护。此外,除非你正在做一些非常棘手的事情,而这些事情不能用 MKL 之类的东西轻松实现,否则你可能不会得到很好的速度提升,除非你真的知道你在做什么。

For the parts that do not require complex operations (which is true for most of my extension): wouldn't (a+0i)(b+0i) be significantly slower than ab ?

是的(除非你用 -ffast-math, IIRC 编译)。但是你可以简单地写:

a.real() * b.real()

无需重写std::complex,它提供了你需要的所有方法

正如 Randomusername 所建议的,我认为不要重新发明轮子是最好的选择。

但是如果大多数场景不需要使用复杂的操作,为什么你不使用 std::complex 的包装来为两种数字(实数和复数)实现 class,一个作为实数的对象和另一个使用相同接口的复数(继承的开销很小但性能良好,这要归功于以最小的变化区分实数-更快-和复杂的域)。在代码中更好地解释:

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); 
};

重写任何运算符都非常困难,但您可以使用 std::complex 实现并通过文字运算优化改进实际运算。

似乎我是少数认为 std::complex 在某些关键的内部循环中可能太慢的人之一,但无论如何这是我的两分钱:前一段时间我是编写一段简单的代码来评估三个变量的复杂多项式并执行一些除法。我注意到,每当我用相应的显式实值算法替换复杂的除法(更多)或乘法(更少)运算符重载(* 或 /)时,代码 运行 明显更快。在替换了大部分乘法和除法之后,我相信速度提高了大约 30% 到 40%。不是一个极端的数量,但足以对抗 运行 在如此有限且关键的代码段中降低可读性。

这是在 GCC 上(我不记得版本了,但在 4.x 中),我检查了复数除法以了解为什么它这么慢。事实证明,它对 Infs 和 NaNs 执行了许多检查,以确保在操作的边缘情况下的正确行为。当然,在做数值运算的时候,得到NaN,反正就输了,所以这个校验不是很想要。我没有检查是否可以关闭此检查。