【问题标题】:How to use Fused Multiply-Add (FMA) instructions with SSE/AVX如何在 SSE/AVX 中使用融合乘加 (FMA) 指令
【发布时间】:2013-04-02 17:20:18
【问题描述】:

我了解到一些 Intel/AMD CPU 可以使用 SSE/AVX 进行同时乘法和加法运算:
FLOPS per cycle for sandy-bridge and haswell SSE2/AVX/AVX2

我想知道如何在代码中做到最好,我也想知道它是如何在 CPU 内部完成的。我的意思是超标量架构。假设我想在 SSE 中做一个很长的总和,如下所示:

//sum = a1*b1 + a2*b2 + a3*b3 +... where a is a scalar and b is a SIMD vector (e.g. from matrix multiplication)
sum = _mm_set1_ps(0.0f);
a1  = _mm_set1_ps(a[0]); 
b1  = _mm_load_ps(&b[0]);
sum = _mm_add_ps(sum, _mm_mul_ps(a1, b1));

a2  = _mm_set1_ps(a[1]); 
b2  = _mm_load_ps(&b[4]);
sum = _mm_add_ps(sum, _mm_mul_ps(a2, b2));

a3  = _mm_set1_ps(a[2]); 
b3  = _mm_load_ps(&b[8]);
sum = _mm_add_ps(sum, _mm_mul_ps(a3, b3));
...

我的问题是如何将其转换为同时乘法和加法?数据可以依赖吗?我的意思是CPU可以同时做_mm_add_ps(sum, _mm_mul_ps(a1, b1))还是做乘法和加法中使用的寄存器必须是独立的?

最后,这如何适用于 FMA(使用 Haswell)? _mm_add_ps(sum, _mm_mul_ps(a1, b1))是自动转换成单条FMA指令还是微操作?

【问题讨论】:

    标签: c sse cpu-architecture avx fma


    【解决方案1】:

    允许编译器融合分离的加法和乘法,即使这会改变最终结果(通过使其更准确)。

    FMA 只有一次舍入(它有效地为内部临时乘法结果保持无限精度),而 ADD + MUL 有两个。

    #pragma STDC FP_CONTRACT ONcompilers are allowed to have it ON by default 生效时,IEEE 和 C 标准允许这样做(但并非全部都这样做)。默认情况下,Gcc 与 FMA 签约(默认为-std=gnu*,但不是-std=c*,例如-std=c++14)。 For Clang,它只能通过-ffp-contract=fast 启用。 (仅启用 #pragma,仅在像 a+b*c 这样的单个表达式中,而不是跨单独的 C++ 语句。)。

    这与允许其他类型的优化 that could increase the rounding error depending on the input values 的严格浮点与宽松浮点(或在 gcc 术语中,-ffast-math-fno-fast-math)不同。这个是特殊的,因为 FMA 内部临时的无限精度;如果内部临时有任何舍入,这在严格的 FP 中是不允许的。

    即使您启用了宽松浮点,编译器仍可能选择不融合,因为如果您已经在使用内在函数,它可能希望您知道自己在做什么。


    所以最好的方法确保你真正得到你想要的 FMA 指令是你实际使用为它们提供的内在函数:

    FMA3 Intrinsics:(AVX2 - 英特尔 Haswell)

    • _mm_fmadd_pd(), _mm256_fmadd_pd()
    • _mm_fmadd_ps(), _mm256_fmadd_ps()
    • 还有无数其他变体...

    FMA4 Intrinsics:(XOP - AMD 推土机)

    • _mm_macc_pd(), _mm256_macc_pd()
    • _mm_macc_ps(), _mm256_macc_ps()
    • 还有无数其他变体...

    【讨论】:

    • 谢谢,这或多或少回答了我关于 FMA 的问题。我真的应该花一些时间学习一些 x86 汇编。这可能会回答我的大部分问题。
    • 关于乘法和加法是否可以同时进行(FMA)的问题。答案是否定的,因为加法使用了乘法的结果。所以你吃掉了加法+乘法的延迟。 FMA 指令同时执行两条指令 - 通常具有与单个乘法相同的延迟。所以添加是免费的。
    • 谢谢,我就是这么想的。现在我只需要弄清楚如何组织我的代码,这样我上面定义的总和就可以同时进行独立的加法和乘法(这样我就可以避免延迟)。
    • 您只需要尽可能多地分离它们即可达到最大吞吐量。关键路径是添加。 addps 的延迟为 3 个周期。但是吞吐量是 1。因此,您至少需要 3 个单独的总和链才能充分利用它。你目前有 4 个,这就足够了。
    • 我认为您的回答具有误导性,因为编译器可以默认使用 FMA 而不会违反 IEEE 规则stackoverflow.com/a/34817983/2542702
    【解决方案2】:

    我在 GCC 5.3、Clang 3.7、ICC 13.0.1 和 MSVC 2015(编译器版本 19.00)中测试了以下代码。

    float mul_add(float a, float b, float c) {
        return a*b + c;
    }
    
    __m256 mul_addv(__m256 a, __m256 b, __m256 c) {
        return _mm256_add_ps(_mm256_mul_ps(a, b), c);
    }
    

    使用正确的编译器选项(见下文),每个编译器都会从mul_add 生成一条vfmadd 指令(例如vfmadd213ss)。但是,只有 MSVC 无法将 mul_addv 收缩为单个 vfmadd 指令(例如 vfmadd213ps)。

    以下编译器选项足以生成 vfmadd 指令(带有 MSVC 的 mul_addv 除外)。

    GCC:   -O2 -mavx2 -mfma
    Clang: -O1 -mavx2 -mfma -ffp-contract=fast
    ICC:   -O1 -march=core-avx2
    MSVC:  /O1 /arch:AVX2 /fp:fast
    

    GCC 4.9 不会将 mul_addv 收缩为单个 fma 指令,但至少 GCC 5.1 会这样做。我不知道其他编译器什么时候开始这样做的。

    【讨论】:

    • 另见#pragma STDC FP_CONTRACT ON。 Stephen Canon 指出它只允许在单个语句内收缩,而不是跨语句。 (lists.llvm.org/pipermail/cfe-dev/2015-September/045110.html)。另请注意,gcc 仅使用 -std=gnu* 启用收缩,而不使用 -std=c11 或其他任何东西。 (然后它允许跨语句收缩,超出 IEEE + ISO C 严格允许的范围)。另一个使用单独变量的测试函数可能值得一试。
    • @PeterCordes,请参阅stackoverflow.com/q/34436233/2542702 和 Stephen Canon 的回答。根据斯蒂芬的回答,我认为 GCC 正在做的事情是可以的(假设 GCC 没有忽略STDC FP_CONTRACT,不幸的是我上次检查时这样做了)。
    • 你的问题只涉及return a*b + c;,而不是float mul = a*b; return mul + c;。仔细阅读 Stephen 的邮件列表帖子:他提到 clang 的 STDC FP_CONTRACT ON 只能在表达式内启用收缩,与 clangs -ffp-contract=fast 不同,后者也可以用于我在此评论中的第二个示例。这就是为什么 clang 对命令行选项有单独的 onfast 设置。请参阅我最近对 ​​Mysticial 对此问题的回答所做的编辑。它比我最初想象的要混乱:(
    • @PeterCordes,我的观点之一是 GCC 忽略了#pragma STDC FP_CONTRACT。至少上次我检查过。我应该再次检查一下(例如 gnuc99 和 c99 或其他)。
    • 我认为这仍然是正确的。它的实际行为超出了#pragma STDC FP_CONTRACT ON 所允许的范围,因此它不像将其默认为 ON 并且未能提供关闭它的方法。我认为从我读到的内容来看,IEEE + C 没有指定#pragma STDC FP_CONTRACT FAST,即使这是一个有用的设置。
    猜你喜欢
    • 2023-03-24
    • 2015-05-19
    • 1970-01-01
    • 1970-01-01
    • 2016-08-26
    • 2014-03-17
    • 1970-01-01
    • 1970-01-01
    • 2013-09-22
    相关资源
    最近更新 更多