我试图通过查看生成的汇编代码来了解使用带有双参数的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?我的假设中有什么错误?
如果您的问题仅与内存操作数有关,请务必注意,mulsd
和addsd
在您的示例中也是内存操作。内存操作由寄存器名称两边的方括号指示,而不是程序集助记符本身。
如果您仍然好奇使用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