【问题标题】:Auto vectorization on double and ffast-mathdouble 和 ffast-math 上的自动矢量化
【发布时间】:2011-02-20 14:48:51
【问题描述】:

为什么强制使用-ffast-math和g++来实现使用doubles的循环向量化?我不喜欢-ffast-math,因为我不想失去精确度。

【问题讨论】:

  • -ffast-math 实际上是一个组合标志,它设置了一组可以单独启用的其他标志 - 也许您只需要设置一个或两个单独的标志就可以逃脱?
  • 我试过了,但只有 --fast-math 我得到了最大数量的矢量化循环

标签: gcc g++ double vectorization fast-math


【解决方案1】:

-ffast-math 不一定会丢失精确度。只影响NaNInf等的处理以及操作的执行顺序。

如果您不希望 GCC 重新排序或简化计算的特定代码段,您可以使用 asm 语句将变量标记为正在使用。

例如,以下代码对f 执行舍入操作。但是,f += gf -= g 这两个操作很可能会被 gcc 优化掉:

static double moo(double f, double g)                                      
{                                                                          
    g *= 4503599627370496.0; // 2 ** 52                                    
    f += g;                                                                
    f -= g;                                                                
    return f;                                                            
}                                                                     

在 x86_64 上,您可以使用此 asm 语句来指示 GCC 不执行该优化:

static double moo(double f, double g)                                      
{                                                                          
    g *= 4503599627370496.0; // 2 ** 52                                    
    f += g;                                                                
    __asm__("" : "+x" (f));
    f -= g;
    return f;
}

不幸的是,您需要针对每种架构进行调整。在 PowerPC 上,使用 +f 而不是 +x

【讨论】:

  • 简单地把f变成volatile double应该有同样的效果。
  • 不幸的是,并非如此。使用volatile 有一个副作用,即变量总是被写入内存。在此示例中,所有操作都可以在寄存器中执行,但使用volatile,编译器将发出几个额外的读/写操作码。所以即使结果一样,代码也会慢很多。
  • 感谢山姆的解释!
【解决方案2】:

很可能是因为矢量化意味着您可能会得到不同的结果,或者可能意味着您错过了浮点信号/异常。

如果您正在为 32 位 x86 编译,那么 gcc 和 g++ 默认使用 x87 进行浮点数学运算,在 64 位上它们默认使用 SSE,但是 x87 可以并且将为相同的计算产生不同的值,所以如果 g++ 不能保证你会得到相同的结果,那么它不太可能考虑向量化,除非你使用 -ffast-math 或它打开的一些标志。

基本上它归结为矢量化代码的浮点环境可能与非矢量化代码的浮点环境不同,有时在某些方面很重要,如果差异对您来说并不重要,例如

-fno-math-errno -fno-trapping-math -fno-signaling-nans -fno-rounding-math

但首先查看这些选项并确保它们不会影响程序的正确性。 -ffinite-math-only 可能也有帮助

【讨论】:

  • 我认为 gcc 实际上并不太注意 x87 与 SSE 舍入模式和其他设置(如非正规 FTZ 和 DAZ)。相关:gcc.gnu.org/bugzilla/show_bug.cgi?id=34678:更改 FP 操作之间的舍入模式需要#pragma STDC FENV_ACCESS ON,即使这样 gcc 也不完全支持。但我认为 gcc 在优化本身不会改变舍入模式的代码时通常不会假设该模式是舍入到最近的。
  • @PeterCordes 在 x87 上,只有 80 位扩展 x87 类型无法使用完全舍入的浮点/双精度来执行某些操作。不记得几年前我在阅读一篇文章时学到了什么。 gcc 确实避免了它过去不恒定折叠浮点的那些事情,因为某个地方的某人可能会交叉编译到奇怪的东西上,结果也不一样,这些天他们有一个内部库可以模拟奇怪的浮点平台所以只要结果不变,它就会不断折叠。
  • 无论如何都没有的方式。 gcc -ffloat-store 存储/重新加载以舍入到 floatdouble,但当然这会消耗大量性能。此外,x87 有一个精度控制位,您可以将其设置为将尾数精度限制为 53 或 24(用于更快的 div/sqrt),而不是 64。请参阅randomascii.wordpress.com/2012/03/21/…:显然 MSVC 过去实际上将其设置为降低精度!
【解决方案3】:

因为-ffast-math 启用了操作数重新排序,它允许对许多代码进行矢量化。

例如计算这个

sum = a[0] + a[1] + a[2] + a[3] + a[4] + a[5] + … a[99]

编译器需要在没有-ffast-math的情况下按顺序进行加法运算,因为浮点数学既不是交换的也不是关联的。

这与why compilers can't optimize a*a*a*a*a*a to (a*a*a)*(a*a*a) 没有-ffast-math 的原因相同

这意味着除非您有非常有效的水平矢量添加,否则没有可用的矢量化。

但是如果启用-ffast-math,则表达式可以计算like this(看A7. Auto-Vectorization

sum0 = a[0] + a[4] + a[ 8] + … a[96]
sum1 = a[1] + a[5] + a[ 9] + … a[97]
sum2 = a[2] + a[6] + a[10] + … a[98]
sum3 = a[3] + a[7] + a[11] + … a[99]
sum’ = sum0 + sum1 + sum2 + sum3

现在编译器可以通过并行添加每一列然后在末尾进行水平添加来轻松对其进行矢量化

sum’ == sum 是吗?仅当 (a[0]+a[4]+…) + (a[1]+a[5]+…) + (a[2]+a[6]+…) + ([a[3]+a[7]+…) == a[0] + a[1] + a[2] + … 这始终在关联性下成立,浮动不遵守,所有时间。指定 /fp:fast 让编译器可以将您的代码转换为运行得更快——对于这个简单的计算,最多可以快 4 倍。

Do You Prefer Fast or Precise? - A7。自动矢量化

它可以通过 gcc 中的-fassociative-math 标志启用

进一步阅读

【讨论】:

    【解决方案4】:

    要使用 gcc 启用自动矢量化,实际上不需要 ffast-math。见https://gcc.gnu.org/projects/tree-ssa/vectorization.html#using

    要启用浮点缩减的矢量化,请使用 -ffast-math 或 -fassociative-math。

    使用 -fassociative-math 就足够了。

    自 2007 年以来就是这种情况,请参阅 https://gcc.gnu.org/projects/tree-ssa/vectorization.html#oldnews

    1. -fassociative-math 可以用来代替 -ffast-math 来实现浮点数减少的矢量化 (2007-09-04)。

    【讨论】:

      猜你喜欢
      • 2013-08-14
      • 2011-11-07
      • 2017-10-23
      • 2011-11-17
      • 2018-12-16
      • 2018-08-03
      • 2010-09-29
      • 1970-01-01
      • 2015-09-01
      相关资源
      最近更新 更多