十年或两年前,编写数字代码以避免使用乘法和除法并使用加法和减法是值得的。一个很好的例子是使用forward differences来计算多项式曲线,而不是直接计算多项式。
是否仍然如此,或者现代计算机架构已经发展到*,/不再比+慢很多倍, - ?
具体来说,我对在现代典型x86芯片上运行的编译C / C ++代码感兴趣,这些代码具有广泛的板载浮点硬件,而不是一个小型微软试图在软件中进行FP。我意识到流水线和其他架构增强功能排除了特定的循环计数,但我仍然希望获得有用的直觉。
答案 0 :(得分:22)
这还取决于教学组合。您的处理器将随时有多个计算单元,如果所有这些单元都被填满,您将获得最大吞吐量。因此,执行mul的循环与执行循环或添加循环一样快 - 但如果表达式变得更复杂,则相同的情况不会成立。
例如,采取这个循环:
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位proc上使用双精度需要6.0秒。如果我用
替换循环bla += 2.1 * arr1[i] + arr2[i] + arr3[i] * arr4[i] ;
只需1.7秒......所以既然我们“过度”添加,那么muls基本上是免费的;增加的减少有助于。它变得更加混乱:
bla += 2.1 + arr1[i] * arr2[i] + arr3[i] * arr4[i] ;
- 相同的mul / add分布,但现在添加常数而不是乘以 - 需要3.7秒。您的处理器可能经过优化,可以更有效地执行典型的数值计算;所以像muls和缩放总和之和的点积就差不多了;添加常量并不常见,因此速度较慢......
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*/
(主要是muls,但一次加法: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 :(得分:18)
理论上,信息在这里:
对于他们列出的每个处理器,FMUL上的延迟非常接近FADD或FDIV的延迟。在一些较旧的处理器上,FDIV比它慢2-3倍,而在较新的处理器上,它与FMUL相同。
注意事项:
我链接的文件实际上说你不能在现实生活中依赖这些数字,因为如果正确的话,处理器会按照它希望的方式做得更快。
您的编译器很有可能决定使用具有浮点乘法/除法的许多新指令集之一。
这是一个复杂的文档,只能由编译器编写者阅读,我可能会弄错。就像我不清楚为什么某些CPU完全没有FDIV延迟数。
答案 2 :(得分:7)
回答这个问题的最佳方法是实际编写您需要处理的基准/配置文件。在可能的情况下,经验应该用于理论。特别是当它很容易实现时。
如果您已经了解了数学的不同实现,那么您可以编写几个不同的数学代码转换函数,并查看性能达到峰值的位置。这将允许处理器/编译器生成不同的执行流来填充处理器流水线,并为您的答案提供具体的答案。
如果您对DIV / MUL / ADD / SUB类型指令的性能感兴趣,您甚至可以投入一些内联汇编来专门控制这些指令的哪些变体被执行。但是,您需要确保多个执行单元保持忙碌,以便更好地了解系统的性能。
同样做这样的事情可以让你通过简单地在它们上运行相同的程序来比较处理器的多种变体的性能,并且还可以让你考虑到主板差异。
编辑:
+的基本架构 - 是相同的。所以他们在逻辑上花了相同的时间来计算。 *另一方面,需要多个层,通常由“全加器”构成,以完成单个操作。这可以说,虽然每个周期都可以向管道发出*,但它的延迟时间比加/减电路要高。 fp /操作通常使用近似方法来实现,该方法随时间迭代地收敛于正确的答案。这些类型的近似通常通过乘法来实现。因此,对于浮点,通常可以假设除法需要更长的时间,因为将多次乘法(已经是一个大的电路和它的自身)“展开”到多个乘法器电路的流水线中是不切实际的。通过测试可以最好地测量给定系统的性能。
答案 3 :(得分:1)
我找不到明确的参考,但是广泛的实验告诉我,现在的浮点乘法与加法和减法的速度大致相同,而除法不是(但也不是“很多次”慢)。你可以通过运行自己的实验来获得你想要的直觉 - 记住提前生成随机数(数百万),在开始计时之前读取它们,并使用CPU的性能计数器(没有其他进程运行,如就像你可以阻止他们进行准确测量一样!
答案 4 :(得分:1)
* / vs +的速度差异取决于您的处理器架构。一般而言,特别是x86,现代处理器的速度差异已经减小。 *如果有疑问,应该接近+:只是实验。如果你有很多FP操作的问题,也可以考虑使用你的GPU(GeForce,...)作为矢量处理器。
答案 5 :(得分:-1)
乘法和加法之间的时间差异可能非常小。另一方面,由于其递归性质,除法仍然明显慢于乘法。 在现代x86架构上,在进行浮点运算时应该考虑sse指令,而不是使用fpu。虽然一个好的C / C ++编译器应该让你选择使用sse而不是fpu。