【问题标题】:How advantageous is using fused multiply-accumulate for double-precision?对双精度使用融合乘法累加有多大优势?
【发布时间】:2020-09-28 01:51:00
【问题描述】:

我试图通过查看生成的汇编代码来了解使用带有双参数的 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?我的假设有什么错误?

【问题讨论】:

  • 这不是一个真正的答案,但如果您删除对abc 的引用,那么test_2 的程序集就变成了jmp fma 调用, test_1 变成 3 条指令。 (compiler explorer 上的示例)
  • -O3 选项对您的指令集一无所知。我刚刚在两个编译器中添加了-march=native,你的两个函数变得等价(并使用vfmadd213sd指令)。顺便说一句,mulsdaddsd 指令包含 移动操作(即从内存中检索数据)。
  • 所有三个都必须按照您的代码要求执行内存周期,相同的数字。但是通过使用函数,test_2 可能会变慢。如果优化器可以识别乘法累加并被编程为使用它,那么调用函数总是比让编译器生成它要慢。如果它不能优化,那么它可以去任何一种方式。如果您处理事物的地址而不是事物本身,那么您对性能不感兴趣。所以如何计算是次要的。
  • 您的标题具有误导性,暗示您想使用特定指令,但您的实现在很大程度上放弃了通过保存指令可能会看到的性能提升。这个问题应该更像是使用函数与内联生成的代码有什么好处。
  • 如果这些是内联处理而不是在函数调用中处理,可能会发生很多差异——避免内存提取、指令重新排序、重叠等。

标签: c++ performance assembly x86-64 fma


【解决方案1】:

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

如果您仍然好奇使用 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 位,将 c 添加到此值然后存储结果为 64 位。

如果您想最大限度地减少计算中的浮点算术错误,std::fma 可能是更好的选择,因为它保证您只会从宝贵的浮点数中剥离一次宝贵的位。


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

【讨论】:

  • 另外值得指出的是内存引用是 OP 的错误,因为它通过引用而不是值传递(已经在 XMM 寄存器中)。
  • 但是是的,如果std::fma 可以内联到单个指令,它通常会更好地提高吞吐量,有时还会降低延迟。 (尽管 gcc 已经将 mul/add 收缩到 FMA 中,并且 clang 可以选择在 GCC 的 default 等语句中积极地执行此操作,因此您通常不需要手动使用 std::fma。)但是如果没有硬件 FMA 支持,@ 987654340@ 速度非常慢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-08-02
  • 1970-01-01
  • 2016-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-24
相关资源
最近更新 更多