【问题标题】:gcc 4.8 AVX optimization bug: extra code insertion?gcc 4.8 AVX 优化错误:额外的代码插入?
【发布时间】:2016-04-07 17:57:15
【问题描述】:

gcc 编译器 4.8 带有带有 -Ofast 选项的 AVX 优化,这很棒。然而,我发现了一个有趣但愚蠢的错误,它增加了不必要的额外计算。也许我错了,谁能给我一个解释?

原C++源代码如下:

#define N 1000007

float a[N],b[N],c[N],d[N],e[N];

int main(int argc, char *argv[]){
    cout << a << ' ' << b << ' ' << c << endl;
    for(int x=0; x<N; ++x){
        c[x] = 1/sqrt((a[x]+b[x]-c[x])*d[x]/e[x]);
    }
    return  0;
}

代码在 Ubuntu 14.04.3 x86_64 中使用 g++ 4.8.4 编译: g++ -mavx avx.cpp -masm=intel -c -g -Wa,-ahl=avx.asm -Ofast

汇编源码如下:

  90                    .LVL10:
  91 006b C5FC2825              vmovaps ymm4, YMMWORD PTR .LC0[rip]
  91      00000000 
  92 0073 31C0                  xor     eax, eax
  93 0075 C5FC281D              vmovaps ymm3, YMMWORD PTR .LC1[rip]
  25:avx.cpp       ****         for(int x=0; x<N; ++x){
  26:avx.cpp       ****                 c[x] = 1/sqrt((a[x]+b[x]-c[x])*d[x]/e[x]);
 101                            .loc 1 26 0 discriminator 2
 102 0080 C5FC2890              vmovaps ymm2, YMMWORD PTR b[rax]
 102      00000000 
 103 0088 4883C020              add     rax, 32
 104 008c C5FC2888              vmovaps ymm1, YMMWORD PTR e[rax-32]
 104      00000000 
 105 0094 C5EC5890              vaddps  ymm2, ymm2, YMMWORD PTR a[rax-32]
 105      00000000 
 106 009c C5FC53C1              vrcpps  ymm0, ymm1
 107 00a0 C5FC59C9              vmulps  ymm1, ymm0, ymm1
 108 00a4 C5FC59C9              vmulps  ymm1, ymm0, ymm1
 109 00a8 C5EC5C90              vsubps  ymm2, ymm2, YMMWORD PTR c[rax-32]
 109      00000000 
 110 00b0 C5FC58C0              vaddps  ymm0, ymm0, ymm0
 111 00b4 C5EC5990              vmulps  ymm2, ymm2, YMMWORD PTR d[rax-32]
 111      00000000 
 112 00bc C5FC5CC9              vsubps  ymm1, ymm0, ymm1
 113 00c0 C5EC59C1              vmulps  ymm0, ymm2, ymm1
 118 00c4 C5FC52C8              vrsqrtps        ymm1, ymm0
 119 00c8 C5F459C0              vmulps  ymm0, ymm1, ymm0
 120 00cc C5FC59C1              vmulps  ymm0, ymm0, ymm1
 121 00d0 C5F459CB              vmulps  ymm1, ymm1, ymm3
 122 00d4 C5FC58C4              vaddps  ymm0, ymm0, ymm4
^LGAS LISTING /tmp/ccJtIFtg.s                   page 21


 123 00d8 C5FC59C9              vmulps  ymm1, ymm0, ymm1
 124                    .LBE45:
 125                    .LBE44:
 126                            .loc 1 26 0 discriminator 2
 127 00dc C5FC2988              vmovaps YMMWORD PTR c[rax-32], ymm1
 127      00000000 
 128 00e4 483D0009              cmp     rax, 4000000
 128      3D00
 129 00ea 7594                  jne     .L3

现在查看第 106、107、108、110、112 和 113 行。

编译器使用乘以它的逆来计算除以 e[x]。所以第 106 行计算了 1/e[x],这是正确的。之后,它可以直接将其与 (a[x]+b[x]-c[x])*d[x] 的最终乘积相乘,该乘积存储在 ymm2 的第 111 行。但是,不是这样做,而是编译器做了一些有趣而荒谬的事情:

  1. 它首先将计算出的倒数 1/e[x] 与 e[x] 相乘以 获取 1(第 107 行)

  2. 然后将此 1 与 1/e[x] 相乘得到 1/e[x](第 108 行)

  3. 然后它把 1/e[x] 添加到自身得到 2/e[x](第 110 行)

  4. 然后将 2/e[x] 减去 1/e[x] 得到 1/e[x](第 112 行)

之后,编译器巧妙地使用 vrsqrtps 指令计算 1/sqrt()。然而,在那之后,会发生什么?它没有在 ymm1 中提取输出(第 118 行),而是再次做了一些更奇特的事情:

  1. 首先将 1/sqrt(x) 乘以 x 得到 sqrt(x),(第 119 行)

  2. 然后将 sqrt(x) 乘以 1/sqrt(x) 得到 1,(第 120 行)

  3. 然后将 1/sqrt(x) 乘以 1(预先存储在 ymm3 中)得到相同的 1/sqrt(x),(第 121 行)

  4. 然后将得到的1加0(预存在ymm4中)得到1,(第122行)

  5. 然后将 1/sqrt(x) 与获得的 1 相乘,得到相同的 1/sqrt(x),(第 123 行)

以上两个冗余表明,每当需要 1/x 时,编译器倾向于将已经获得的输出与原始数字相乘以返回 1,然后将这个 1 与已经获得的输出相乘以返回相同的输出.这样做有什么理由吗?或者它只是一个错误?

【问题讨论】:

  • 使用g++ -Ofast -S -fverbose-asm -fdump-tree-all 编译并查看生成的 .s 文件以了解有关优化的更多信息;顺便说一句,你的 GCC 已经很老了,目前是 GCC 5.3 2016 年 1 月上旬。

标签: gcc optimization g++ sse avx


【解决方案1】:

我认为您在生成的代码中看到的是对Newton-Raphson 的额外迭代,以优化vrcpps 提供的初始估计。 (有关vrcpps 提供的初始估计准确度的详细信息,请参阅:Intel Intrinsics Guide。)

【讨论】:

【解决方案2】:

我已经找到原因了。所有的 AVX/SIMD/SSE 逼近指令都需要至少一次 Newton-Rhapson 迭代才能恢复精度,否则会损失 50% 的精度,即原始 FLOAT32 的精度高达 23 位。没有任何 Newton-Rhapson,我们只剩下 11 位精度。这个近似值太粗糙了,无法直接使用。

【讨论】:

    猜你喜欢
    • 2014-12-31
    • 2011-12-16
    • 2020-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多