使用融合乘法累加实现双精度有多大优势?



我试图通过查看生成的汇编代码来了解使用带有双参数的std::fma是否有利,我正在使用标志"-O3",并且我正在比较这两个例程的程序集:

#include <cmath>
#define FP_FAST_FMAF
float test_1(const double &a, const double &b, const double &c ){
return a*b + c;
}
float test_2(const double &a, const double &b, const double &c ){
return std::fma(a,b,c);
}

使用编译器资源管理器工具,这是为两个例程生成的程序集:

test_1(double const&, double const&, double const&):
movsd     xmm0, QWORD PTR [rdi]                         #5.12
mulsd     xmm0, QWORD PTR [rsi]                         #5.14
addsd     xmm0, QWORD PTR [rdx]                         #5.18
cvtsd2ss  xmm0, xmm0                                    #5.18
ret                                                     #5.18
test_2(double const&, double const&, double const&):
push      rsi                                           #7.65
movsd     xmm0, QWORD PTR [rdi]                         #8.12
movsd     xmm1, QWORD PTR [rsi]                         #8.12
movsd     xmm2, QWORD PTR [rdx]                         #8.12
call      fma                                           #8.12
cvtsd2ss  xmm0, xmm0                                    #8.12
pop       rcx                                           #8.12
ret      

并且程序集不会因使用 ICC 或 gcc 可用的最新版本而更改。 关于这两个例程的性能,令我感到困惑的是,虽然对于test_1只有一个内存操作(movsd(,但有三个用于test_2,并且考虑到内存操作的延迟比浮点操作的延迟大一到两个数量级, test_1应更具性能。因此,在哪些情况下建议使用 std::fma?我的假设中有什么错误?

如果您的问题仅与内存操作数有关,请务必注意,mulsdaddsd在您的示例中也是内存操作。内存操作由寄存器名称两边的方括号指示,而不是程序集助记符本身。

如果您仍然好奇使用std::fma是否有利,答案可能是"视情况而定"。

当您通过查看汇编来分析性能时,几乎必须至少向编译器提供有关目标体系结构的一些信息。std::fma使用硬件 FMA 指令(如果它们在目标体系结构上可用(,因此std::fma是否总体上提高性能并不是一个真正可以回答的问题。

如果在编译器资源管理器中指定-mfma,编译器将有一些可用于生成更高效代码的信息。您还可以指定-march=[your architecture]如果支持,这将自动为您设置-mfma


此外,还有一整堆蠕虫关于std::fma(a*b)+c的结果的细微差异,这是由于使用浮点数处理舍入的方式。std::fma在两个浮点运算中只舍入一次,而(a*b)+c可能[1]a*b,以 64 位存储结果,将c加到此值,然后将结果存储为 64 位。

如果你想最小化计算中的浮点算术误差,std::fma可能是更好的选择,因为它保证你只会从宝贵的浮点数中剥离一次宝贵的位。


[1]是否会发生这种额外的舍入取决于您的编译器、优化设置和架构设置: 编译器资源管理器示例 msvc, gcc, icc, clang

相关内容

  • 没有找到相关文章

最新更新