我试图通过查看生成的汇编代码来了解使用带有双参数的 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);
}
使用Compiler Explorer工具,这是为两个例程生成的程序集:
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?我的假设中有什么错误?
答案 0 :(得分:1)
如果您的问题仅与内存操作的数量有关,请注意,在您的示例中,mulsd
和addsd
也是内存操作。存储器操作由寄存器名称周围的方括号指示,而不是由汇编助记符本身表示。
如果您仍然好奇使用std::fma
是否有利,答案可能是“视情况而定”。
当您通过查看汇编来分析性能时,为编译器至少提供一些有关目标体系结构的信息几乎是至关重要的。 std::fma
使用目标架构上可用的硬件FMA指令,因此,std::fma
是否总体上可以提高性能并不是一个真正可以回答的问题。
如果您specify -mfma
in Compiler Explorer,则编译器具有一些信息,可以利用它来生成更有效的代码。您还可以指定-march=[your architecture]
,如果受支持,它将自动为您设置-mfma
。
另外,由于使用浮点数舍入的方式,关于std::fma
和(a*b)+c
的结果中的细微差异,还有另一种蠕虫病毒。 std::fma
在两个浮点运算中仅舍入一次,而(a*b)+c
可能 [1] 执行a*b
,将结果存储在64位中,加{{1 }}到此值,然后将结果存储在64位中。
如果要在计算中最大程度地减少浮点算术错误,c
可能是一个更好的选择,因为它可以确保仅从宝贵的浮点数中剥离掉一些宝贵的位。
[1] 是否进行此额外舍入取决于编译器,优化设置和体系结构设置: Compiler Explorer个用于msvc,gcc,icc,clang的示例