4

在 icc 19 上,点积编译为 fma 指令上的循环。在 clang 和 gcc 上,fma 仅使用-ffast-math.

但是,-ffast-math违反了 IEEE 合规性,但 fma 完全符合 IEEE-754 2008,所以如果我必须使用 编译-ffast-math,那么我会导致其他问题。

为什么 gcc 和 clang 不生成 fma 指令-ffast-math

神螺栓;编译器标志是-O3 -march=skylake-avx512, +- -ffast-math

4

1 回答 1

3

编译器是否适合实现使用融合乘法/加法编写的点积dot({a,c}, {b,d}) := a*b + c*d,给出 fl(⋅ + fl(⋅)) 就好像它是编写的一样fma(a,b, c*d)?一般不会!

以下是根据 W. Kahan 在 IEEE 754 上的讲义改编的几个示例:

  • 假设我们要评估平方差² − ²。

    这可以写成点积dot({x,y}, {-x,y}) = x*x - y*y。这个幼稚的公式在 ≈ 时会遭受灾难性的抵消,但至少在 = 时,它可靠地返回零,因为 fl(fl(²) − fl(²)) = fl(fl(²) − fl(²)) = fl( 0) = 0。

    这可以用 FMA 作为 来计算fma(x,x, -y*y)。但是如果 = = fl(1.234) = 0x1.3be76c8b43958p+0 那么结果是 -1.3532e7b3d8ep-55 ≈ -3.352 × 10⁻¹⁷,在 IEEE 754 binary64 算术中,而不是我们可能希望的零。

    它不仅非零,而且是负数,所以如果你尝试在下游取平方根,即使你可以保证上游 ≥ ,你也会遇到 NaN 。

    (当然,分解(x + y)*(x - y)可以更好地避免中间的灾难性取消,但这个问题是关于在没有额外假设的情况下评估点积。)

  • 假设我们要在直角坐标 ( + )⋅( + ) = ( − ) + ( + ) 中评估复数乘积。

    它的虚部可以写成点积dot({a,d}, {b,c}) = a*d + b*c。它可以用 FMA 作为 来计算fma(a,d, b*c)

    你可能期望复数 + 与其复共轭 - 的乘积是实数,虚部为零——如果用 计算,它确实如此a*d + b*c,但如果用 计算,则不是fma(a,d, b*c)。例如,如果 = fl(1.234) = 1.3be76c8b43958p+0 和 = fl(5.678) = 1.6b645a1cac083p+2,则 fl(⋅(−) + fl(⋅)) = −1.6f6512a94ffp−55 ≈ 3.983 × 10⁻ ¹⁷。

因此,在这些场景中使用 FMA 是一种糟糕的编译器形式,而无需通过fma(a,b, c*d)使用fma函数 from编写<math.h>或添加#pragma STDC FP_CONTRACT ON以授权此类恶作剧来明确要求它。


就是说……说服 GCC 10.2 滥用 vfmadd231sd似乎并不难,只需传递-O2 -march=haswell,即使是明确的#pragma STDC FP_CONTRACT OFF对于 ICC 21.1.9 也是如此。这在我看来就像一个有缺陷的优化器!相比之下,Clang 11.0.1 使用带有 的 vfmadd231sd#pragma STDC FP_CONTRACT ON,但不使用省略或设置为的编译指示OFF

于 2021-02-12T07:23:19.923 回答