J. *_*son 29 floating-point x86 mips numerical-computing flops
十年或两年前,编写数字代码以避免使用乘法和除法并使用加法和减法是值得的.一个很好的例子是使用前向差异来评估多项式曲线,而不是直接计算多项式.
是否仍然如此,或者现代计算机架构已经发展到*,/不再比+慢很多倍, - ?
具体来说,我对在现代典型x86芯片上运行的编译C/C++代码感兴趣,这些代码具有广泛的板载浮点硬件,而不是一个小型微软试图在软件中进行FP.我意识到流水线和其他架构增强功能排除了特定的循环计数,但我仍然希望获得有用的直觉.
Eam*_*nne 23
它还取决于教学组合.您的处理器将随时有多个计算单元,如果所有这些单元都被填满,您将获得最大吞吐量.因此,执行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] ;
}
}
Run Code Online (Sandbox Code Playgroud)
对于NUMITER = 10 ^ 7,NUMEL = 10 ^ 2,两个数组都初始化为小正数(NaN慢得多),这在64位proc上使用双精度需要6.0秒.如果我用循环替换循环
bla += 2.1 * arr1[i] + arr2[i] + arr3[i] * arr4[i] ;
Run Code Online (Sandbox Code Playgroud)
它只需1.7秒......所以既然我们"过度"添加,那么muls基本上是免费的; 增加的减少有助于.它变得更加混乱:
bla += 2.1 + arr1[i] * arr2[i] + arr3[i] * arr4[i] ;
Run Code Online (Sandbox Code Playgroud)
- 相同的mul/add分布,但现在常量被加入而不是乘以 - 需要3.7秒.您的处理器可能经过优化,可以更有效地执行典型的数值计算; 所以像muls和缩放总和之和的点积就差不多了; 添加常量并不常见,因此速度较慢......
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; /*someval == 2.1*/
Run Code Online (Sandbox Code Playgroud)
再花1.7秒.
bla += someval + arr1[i] + arr2[i] + arr3[i] + arr4[i] ; /*someval == 2.1*/
Run Code Online (Sandbox Code Playgroud)
(与初始循环相同,但没有昂贵的恒定加法:2.1秒)
bla += someval * arr1[i] * arr2[i] * arr3[i] * arr4[i] ; /*someval == 2.1*/
Run Code Online (Sandbox Code Playgroud)
(主要是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
Run Code Online (Sandbox Code Playgroud)
Sco*_*yre 19
理论上,信息在这里:
英特尔®64和IA-32架构优化参考手册,附录C指令延迟和吞吐量
对于他们列出的每个处理器,FMUL上的延迟非常接近FADD或FDIV的延迟.在一些较旧的处理器上,FDIV比它慢2-3倍,而在较新的处理器上,它与FMUL相同.
注意事项:
我链接的文件实际上说你不能在现实生活中依赖这些数字,因为如果处理器正确的话,处理器会做它想要的东西.
您的编译器很有可能决定使用具有浮点乘法/除法的许多新指令集中的一个.
这是一个复杂的文档,只能由编译器编写者阅读,我可能已经弄错了.就像我不清楚为什么某些CPU完全缺少FDIV延迟数.
回答这个问题的最好方法是实际编写您需要处理的基准/配置文件.在可能的情况下,经验应该用于理论.特别是当它容易实现时.
如果您已经知道需要做的数学的不同实现,您可以编写几个不同的数学代码转换,并查看性能达到峰值的位置.这将允许处理器/编译器生成不同的执行流来填充处理器管道,并为您的答案提供具体的答案.
如果您对DIV/MUL/ADD/SUB类型指令的性能特别感兴趣,您甚至可以投入一些内联汇编来控制这些指令的哪些变体被执行.但是,您需要确保多个执行单元保持忙碌,以便更好地了解系统的性能.
同样做这样的事情可以让你通过简单地在它们上运行相同的程序来比较处理器的多种变体的性能,并且还可以让你考虑主板差异.
编辑:
+的基本架构是相同的.所以他们在逻辑上花了相同的时间来计算.*另一方面,需要多个层,通常由"全加器"构成,以完成单个操作.这可以说,虽然每个周期都可以向管道发出*,但它的延迟时间比加/减电路要高.fp /操作通常使用近似方法来实现,该方法随时间迭代地收敛于正确的答案.这些类型的近似通常通过乘法来实现.因此,对于浮点,通常可以假设除法需要更长的时间,因为将多次乘法(已经是一个大的电路和它的自身)"展开"到多个乘法器电路的流水线中是不切实际的.通过测试可以最好地测量给定系统的性能.