融合乘加和默认舍入模式

Fused multiply add and default rounding modes

使用 GCC 5.3,以下代码使用 -O3 -fma

编译
float mul_add(float a, float b, float c) {
  return a*b + c;
}

生成以下程序集

vfmadd132ss     %xmm1, %xmm2, %xmm0
ret

I noticed GCC doing this with -O3 already in GCC 4.8.

带有 -O3 -mfma 的 Clang 3.7 生成

vmulss  %xmm1, %xmm0, %xmm0
vaddss  %xmm2, %xmm0, %xmm0
retq

但是带有 -Ofast -mfma 的 Clang 3.7 生成与带有 -O3 fast.

的 GCC 相同的代码

我很惊讶 GCC 使用 -O3 因为从 this answer 它说

The compiler is not allowed to fuse a separated add and multiply unless you allow for a relaxed floating-point model.

This is because an FMA has only one rounding, while an ADD + MUL has two. So the compiler will violate strict IEEE floating-point behaviour by fusing.

然而,从this link它说

Regardless of the value of FLT_EVAL_METHOD, any floating-point expression may be contracted, that is, calculated as if all intermediate results have infinite range and precision.

所以现在很迷茫很担心

  1. GCC 将 FMA 与 -O3 一起使用是否合理?
  2. 融合是否违反严格的 IEEE 浮点行为?
  3. 如果融合确实违反了 IEEE 浮点行为并且因为 GCC returns __STDC_IEC_559__ 这不是矛盾吗?

自从 FMA 看来 FMA 应该有两个编译器开关:一个告诉编译器在计算中使用 FMA,另一个告诉编译器硬件有 FMA。


显然这可以通过选项 -ffp-contract 来控制。对于 GCC,默认值为 -ffp-contract=fast 而对于 Clang,则不是。 -ffp-contract=on-ffp-contract=off 等其他选项不会产生 FMA 指令。

例如带有 -O3 -mfma -ffp-contract=fast 的 Clang 3.7 生成 vfmadd132ss.


我检查了 #pragma STDC FP_CONTRACT 设置为 ONOFF 以及 -ffp-contract 设置为 onofffast。在所有情况下,我还使用了 -O3 -mfma.

有了 GCC,答案就很简单了。 #pragma STDC FP_CONTRACT ON 或 OFF 没有区别。只有 -ffp-contract 重要。

GCC 它使用 fma

  1. -ffp-contract=fast(默认)。

对于 Clang,它使用 fma

  1. -ffp-contract=fast.
  2. -ffp-contract=on(默认)和 #pragma STDC FP_CONTRACT ON(默认为 OFF)。

换句话说,使用 Clang,您可以使用 #pragma STDC FP_CONTRACT ON(因为 -ffp-contract=on 是默认设置)或 -ffp-contract=fast 来获得 fma-ffast-math(因此 -Ofast)设置 -ffp-contract=fast.


我研究了 MSVC 和 ICC。

对于 MSVC,它使用带有 /O2 /arch:AVX2 /fp:fast 的 fma 指令。使用 MSVC /fp:precise 是默认值。

对于 ICC,它使用 fma 和 -O3 -march=core-avx2(实际上 -O1 就足够了)。这是因为默认情况下 ICC 使用 -fp-model fast。但是 ICC 即使 -fp-model precise 也使用 fma。要使用 ICC 禁用 fma,请使用 -fp-model strict-no-fma.

因此默认情况下 GCC 和 ICC 在启用 fma 时使用 fma(-mfma 用于 GCC/Clang 或 -march=core-avx2 用于 ICC)但 Clang 和 MSVC 不使用。

当您引用允许融合乘加时,您遗漏了重要条件 "unless pragma FP_CONTRACT is off"。这是 C 中的一个新特性(我认为是在 C99 中引入的)并且是 PowerPC 绝对必要的,all 从一开始就融合了乘加法——实际上,x*y 是等价的到 fma (x, y, 0) 和 x+y 相当于 fma (1.0, x, y)。

FP_CONTRACT 是控制融合的 multiply/add,而不是 FLT_EVAL_METHOD。尽管如果 FLT_EVAL_METHOD 允许更高的精度,那么契约总是合法的;只是假装这些操作是以非常高的精度执行的,然后四舍五入。

如果您不想要速度,而是想要精度,那么 fma 函数很有用。它会缓慢但正确地计算合同结果,即使它在硬件中不可用。如果它在硬件中可用,则应该内联。

它不违反 IEEE-754,因为 IEEE-754 在这一点上遵从语言:

A language standard should also define, and require implementations to provide, attributes that allow and disallow value-changing optimizations, separately or collectively, for a block. These optimizations might include, but are not limited to:

...

― Synthesis of a fusedMultiplyAdd operation from a multiplication and an addition.

在标准 C 中,STDC FP_CONTRACT 编译指示提供了控制此 value-changing 优化的方法。所以 GCC 被许可在默认情况下执行融合,只要它允许您通过设置 STDC FP_CONTRACT OFF 来禁用优化。不支持那意味着不遵守 C 标准。