我目前正在开发一个前一个开发人员使用
的项目std::complex<long double>
贯穿大部分代码。该软件在很大程度上依赖于信号处理方法,这些方法都是使用上述复杂数据类型实现的。 经常创建,访问和删除大型多维数组。
这种数据类型的好处是,所有必需的数学函数(例如来自<cmath>
)都支持复数,因此使用此数据类型的基本数学运算几乎没有开销。
其他功能,例如大数据量的n维复杂卷积已由我们的软件由所述开发人员实现。
我目前正致力于使用n维卷积A LOT的扩展。但是此扩展的大多数方案 不需要复杂的操作。 该程序目前运行速度很慢,我想知道在关键部分使用专有结构是否会更快。好像
struct CPLX{
long double REAL;
long double IMAG;
}CPLX;
并自己实现所需的方法(实现复数乘法,阶段等数学运算......)。
对于不需要复杂操作的部分(对于我的大多数扩展都是如此):不会
(a+0i)*(b+0i)
明显慢于
a*b
将使用自己的结构,有效实现数学运算和最小开销VS.使用std::complex
和cmath
会更快吗?
(除了事实上这需要额外的测试以确保一切正常)
使用std::complex
时是否存在重大开销?
如果是这样,使用std::complex
何时比使用自己的方法和结构更合适?
答案 0 :(得分:8)
不要重新使用轮子
内置标准库已针对您的硬件进行了优化和调整。不要浪费你的时间来尝试制作一些只是默认值的一小部分的东西。如果您发现在特定例程中,配置文件显示速度较慢,请使用更好的库,例如Intel或GNU的复杂浮点库提供的库。
编辑:不要害怕复杂数字库的可能开销。唯一的内存开销是将实部和虚部一起存储在一个对象中,唯一的时间开销就是实际将它们打包在一起。这两个动作都将由你提出的实现复制,除非你从来没有真正需要复杂的数字。
答案 1 :(得分:3)
对于不需要复杂操作的部件(对于我的大多数扩展都是如此):不会 (a + 0i)(b + 0i)明显慢于 b ?
是的(除非您使用-ffast-math
,IIRC编译)。但你可以写一下:
a.real() * b.real()
无需重写std::complex
,它提供了您需要的所有方法。
答案 2 :(得分:1)
制作自己的结构和手写操作不会做任何事情,只会使你的代码更难阅读,维护更少。
你真正想要的是利用SSE / AVX指令来加快速度。最好的方法是 - 使用像英特尔MKL这样的库(有许可费,但速度非常快) - 看看Agner Fog的矢量库和他的优化手册 - 研究如何编写编译器可以轻松优化为SSE / AVX指令的代码
另外值得注意的是,通过多线程可以加速这些类型的操作,这很容易通过使用支持使用适当的指令进行自动并行化的编译器或通过一些OpenMP来完成(非常有帮助)图书馆知道你是否还没有使用它。)
最后,您可以通过内部库编写自己的SSE / AVX代码,但这是非常耗时的,并且使代码难以维护。另外,除非你做了一些非常棘手的事情,用MKL这样的东西不能轻易实现,否则除非你真的知道自己在做什么,否则你可能无法获得良好的速度提升。
答案 3 :(得分:1)
正如Randomusername建议我认为不重新发明轮子是最好的选择。
但是如果不需要大多数场景使用复杂的操作,为什么你不使用std :: complex上的包装来实现两种数字(真实和复数)的类,一个对象作为实数和另一个使用相同接口的复数(对于继承有一点开销,但由于区分真实 - 更快 - 和复杂的域而且变化很小),表现良好。更好地解释代码:
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实现并通过文字运算优化来改进实际操作。
答案 4 :(得分:1)
似乎我是少数几个认为std :: complex可能在一些关键的内循环中太慢的人之一,但无论如何这里是我的两分钱:前一段时间我写的很简单一段代码,用于评估三个变量中的复数多项式并执行一些除法。我注意到,每当我用相应的显式实值算法替换复数除法(更多)或乘法(更少)运算符重载(*或/)时,代码运行得更快。在替换了大部分乘法和除法后,我相信速度增益约为30%到40%。不是一个极端的数量,但足以保证在这样一个有限的和关键的代码片段中可读性降低。
这是在GCC(我不记得版本,但它是在4.x),我检查了复杂的部门,看看为什么它这么慢。事实证明,它对Infs和NaN执行了许多检查,以确保在操作的边缘情况下的正确行为。当然,在进行数值运算时,当你得到NaN时,无论如何都会丢失,所以这种检查并不是真的需要。我没有检查是否可以关闭此检查。