浮点数加法和浮点数乘法的相对速度是什么?

31
几十年前,编写数值代码以避免使用乘除法并使用加减法代替是值得的。一个很好的例子是使用前向差分来评估多项式曲线而不是直接计算多项式。现代计算机架构是否已经发展到了*,/不再比+,-慢许多的程度?具体而言,我对在现代典型x86芯片上运行的编译C/C++代码感兴趣,该芯片具有广泛的内置浮点硬件,而不是尝试在软件中进行FP的小型微控制器。我意识到流水线和其他架构增强排除了特定的周期计数,但我仍然希望获得有用的直觉。

相关:浮点除法与浮点乘法比较了现代x86微架构的一些内容。简而言之,如果你不经常使用它,divps在吞吐量方面可能还可以,但延迟始终要差得多。 - Peter Cordes
相关:为什么在将乘法转换为循环内加法后,此代码执行速度更慢? 关于绘制二次函数的前向差分。如果不小心,很容易使它变慢,因为在现代x86上,FP加法与FP乘法的性能基本相同。(在Skylake及以后的英特尔CPU上,完全相同的性能,直到Alder Lake通过1个周期降低了addps/pd的延迟。) - Peter Cordes
6个回答

27

这还取决于指令混合。您的处理器将随时准备好多个计算单元,如果所有计算单元始终都被填满,则可以获得最大吞吐量。因此,执行一系列乘法的循环与执行一系列加法的循环一样快-但是如果表达式变得更加复杂,则情况就不同了。

例如,请看这个循环:

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

1
指令混合是一个很好的观点,我与之合作的人坚持认为200浮点DSP将优于600定点DSP。他们绝对不进行紧密循环处理,并且花费更多时间处理I/O而不是进行计算。根据整体指令混合,更快的定点处理器将获胜,但人们只是认为FP单元是魔术,而不是数据结构的硬件实现。 - NoMoreZealots
1
很好的解释,带有直观的例子! - Sebastian Good
这与主题相关,但并没有回答问题。 - Noob
@Noob没有一个正确的答案,这就是我试图解释的。或者换句话说:真正的答案是“取决于”,这并不特别有帮助,特别是因为它取决于的事情数量巨大,并且随着每个新芯片、非常规执行环境、优化编译器、使用的SIMD模式组合等而增长。通常,最好将它们视为大致相等的代价 - 当差异变得有意义时,你也处于需要仔细的逐个案例分析的微观优化水平。 - Eamon Nerbonne

19

理论上,信息在这里:

Intel® 64和IA-32体系结构优化参考手册,附录C 指令延迟和吞吐量

对于每个处理器,FMUL的延迟非常接近于FADD或FDIV。 在某些旧处理器上,FDIV比FMUL慢2-3倍,而在较新的处理器上,FDIV与FMUL相同。

注意事项:

  1. 我链接的文档实际上说,您不能依赖这些数字在实际生活中,因为如果正确的话,处理器将做出使事情更快的决策。

  2. 您的编译器很可能会决定使用具有浮点乘法/除法的许多新指令集之一。

  3. 这是一个复杂的文档,只供编译器编写人员阅读,我可能弄错了。就像我不清楚为什么一些CPU的FDIV延迟数字完全缺失一样。


1
非常酷的文档。我认为一个始终保持一致的事情(并且这个文件显示了它)是,除法仍然比乘法、加法和减法慢得多。从这个文档看来,双精度除法的延迟比乘法慢了10倍。因此,例如,我相信调用x = y * 0.5应该比调用x = y / 2更快。 - Steve Wortham
@SteveWortham 你能告诉我在哪个页面上找到了有关fdiv比fmul慢10倍的信息吗? - 0fnt
@user247077 - 我不记得了。这是几年前的事了。然而,这个文档中有一些图表,涉及许多不同命令的延迟。在这些图表中,FMUL肯定比FDIV更快。然后,在C-33页上有DIV r64和MUL r64,它们之间的延迟差距很大。去年,当我创建一个64位应用程序来测试乘法和除法之间的性能差异时,我可能使用了这些指令(或AMD的等效指令)...http://swortham.blogspot.com/2011/10/how-much-faster-is-multiplication-than.html - Steve Wortham
现代的x86处理器中,基本上仍然适用相同的情况,特别是在使用SSE2浮点数运算时。addsdmulsd非常类似,而divsd则具有更高的延迟和更差的吞吐量浮点除法与浮点乘法(Haswell / Broadwell具有两倍于加法的乘法吞吐量,但加法的延迟至少与乘法一样好。所以这在Skylake之前很奇怪,在Skylake中,加法/乘法在相同的FMA执行单元上都运行相同,具有4个时钟周期的延迟和每秒钟2个时钟周期的吞吐量。https://agner.org/optimize/) - Peter Cordes
这份文档似乎只适用于英特尔 Atom CPU。我在这份文档中没有找到其他CPU类型的数据。 - Noob

9
回答这个问题的最好方法是实际编写一个处理程序的基准/概要。尽可能使用经验法而不是理论法。特别是在易于获得的情况下。
如果您已经知道所需执行的不同数学实现,可以编写数学的几种不同代码转换,并查看性能峰值在哪里。这将允许处理器/编译器生成不同的执行流以填充处理器管道,并为您提供确切的答案。
如果您特别关注DIV/MUL/ADD/SUB类型指令的性能,甚至可以添加一些内联汇编来控制具体执行哪些变量的这些指令。但是,您需要确保保持多个执行单元繁忙,以了解系统的性能。
此外,做这样的事情还可以让您比较多个处理器变体的性能,只需在它们上运行相同的程序,还可以考虑到主板的差异。
编辑:
加减运算的基本架构是相同的。因此,在逻辑上计算时间相同。*另一方面,需要多个层次,通常由“全加器”构成,才能完成单个操作。这保证了每个*可以在每个周期发出到流水线,但与加/减电路相比,它具有更高的延迟。 fp /操作通常使用逐步收敛于正确答案的近似方法实现。这些类型的近似通常通过乘法实现。因此,对于浮点数,您通常可以假设除法需要更长时间,因为将乘法(本身已经是一个大电路)“展开”到多个乘法器电路的流水线中是不切实际的。仍然,最好通过测试来衡量给定系统的性能。

2

虽然我找不到确切的参考资料,但是广泛的实验表明,目前浮点数乘法的速度几乎与加法和减法相同,而除法则稍慢(但也不是“多倍”)。您可以通过运行自己的实验来获得所需的直觉--记得提前生成随机数(数百万个),在开始计时之前将其读取,并使用CPU的性能计数器(尽可能停止其他进程)进行精确测量!


1

* / 与 + - 的速度差异取决于您的处理器架构。一般来说,特别是在 x86 上,随着现代处理器的出现,速度差异已经变得不那么明显了。当有疑问时,* 应该接近于 +,可以进行实验以确定。如果您遇到了大量 FP 操作的困难问题,还可以考虑使用 GPU(GeForce 等),它可以作为矢量处理器。


-1

在乘法和加法之间,时间上可能几乎没有什么区别。然而,由于其递归性质,除法仍然比乘法慢得多。 在现代x86架构中,进行浮点运算时应考虑使用sse指令,而不是使用fpu。尽管一个好的C/C++编译器应该给你使用sse而不是fpu的选项。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接