这还取决于指令混合。您的处理器将随时准备好多个计算单元,如果所有计算单元始终都被填满,则可以获得最大吞吐量。因此,执行一系列乘法的循环与执行一系列加法的循环一样快-但是如果表达式变得更加复杂,则情况就不同了。
例如,请看这个循环:
for(int j=0;j<NUMITER;j++) {
for(int i=1;i<NUMEL;i++) {
bla += 2.1 + arr1[i] + arr2[i] + arr3[i] + arr4[i] ;
}
}
对于NUMITER=10^7,NUMEL=10^2,两个数组都初始化为小正数(NaN要慢得多),使用64位处理器上的双精度数需要6.0秒。如果我用以下循环替换:
bla += 2.1 * arr1[i] + arr2[i] + arr3[i] * arr4[i] ;
仅需1.7秒钟...因此,由于我们“过度”添加了一些,所以乘法实际上是免费的;并且减少了加法也有帮助。它变得更加混乱:
bla += 2.1 + arr1[i] * arr2[i] + arr3[i] * arr4[i] ;
-- 相同的乘法/加法分配,但现在是将常数相加而不是相乘 -- 耗时3.7秒。您的处理器可能已经针对典型的数值计算进行了优化,因此类似于点积的乘法和缩放和的总和是最好的选择;添加常量并不像这样普遍,所以速度较慢...
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; /*someval == 2.1*/
再次需要 1.7 秒。
bla += someval + arr1[i] + arr2[i] + arr3[i] + arr4[i] ; /*someval == 2.1*/
与初始循环相同,但不包括昂贵的常数加法:2.1秒
bla += someval * arr1[i] * arr2[i] * arr3[i] * arr4[i] ; /*someval == 2.1*/
(主要是乘法,但有一个加法:1.9秒)
基本上来说,很难说哪种方法更快,但如果你想避免瓶颈,更重要的是要有一个合理的混合方式,避免NaN或INF,避免添加常数。无论你做什么,一定要测试,并测试各种编译器设置,因为通常小的改变可以产生巨大的差异。
以下是一些其他情况:
bla *= someval; // someval very near 1.0; takes 2.1 seconds
bla *= arr1[i] ;// arr1[i] all very near 1.0; takes 66(!) seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; // 1.6 seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; //32-bit mode, 2.2 seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; //32-bit mode, floats 2.2 seconds
bla += someval * arr1[i]* arr2[i];// 0.9 in x64, 1.6 in x86
bla += someval * arr1[i];// 0.55 in x64, 0.8 in x86
bla += arr1[i] * arr2[i];// 0.8 in x64, 0.8 in x86, 0.95 in CLR+x64, 0.8 in CLR+x86
理论上,信息在这里:
Intel® 64和IA-32体系结构优化参考手册,附录C 指令延迟和吞吐量
对于每个处理器,FMUL的延迟非常接近于FADD或FDIV。 在某些旧处理器上,FDIV比FMUL慢2-3倍,而在较新的处理器上,FDIV与FMUL相同。
注意事项:
我链接的文档实际上说,您不能依赖这些数字在实际生活中,因为如果正确的话,处理器将做出使事情更快的决策。
您的编译器很可能会决定使用具有浮点乘法/除法的许多新指令集之一。
这是一个复杂的文档,只供编译器编写人员阅读,我可能弄错了。就像我不清楚为什么一些CPU的FDIV延迟数字完全缺失一样。
addsd
和mulsd
非常类似,而divsd
则具有更高的延迟和更差的吞吐量。浮点除法与浮点乘法(Haswell / Broadwell具有两倍于加法的乘法吞吐量,但加法的延迟至少与乘法一样好。所以这在Skylake之前很奇怪,在Skylake中,加法/乘法在相同的FMA执行单元上都运行相同,具有4个时钟周期的延迟和每秒钟2个时钟周期的吞吐量。https://agner.org/optimize/) - Peter Cordes虽然我找不到确切的参考资料,但是广泛的实验表明,目前浮点数乘法的速度几乎与加法和减法相同,而除法则稍慢(但也不是“多倍”)。您可以通过运行自己的实验来获得所需的直觉--记得提前生成随机数(数百万个),在开始计时之前将其读取,并使用CPU的性能计数器(尽可能停止其他进程)进行精确测量!
* / 与 + - 的速度差异取决于您的处理器架构。一般来说,特别是在 x86 上,随着现代处理器的出现,速度差异已经变得不那么明显了。当有疑问时,* 应该接近于 +,可以进行实验以确定。如果您遇到了大量 FP 操作的困难问题,还可以考虑使用 GPU(GeForce 等),它可以作为矢量处理器。
在乘法和加法之间,时间上可能几乎没有什么区别。然而,由于其递归性质,除法仍然比乘法慢得多。 在现代x86架构中,进行浮点运算时应考虑使用sse指令,而不是使用fpu。尽管一个好的C/C++编译器应该给你使用sse而不是fpu的选项。
divps
在吞吐量方面可能还可以,但延迟始终要差得多。 - Peter Cordesaddps
/pd
的延迟。) - Peter Cordes