【问题标题】:64 bit code generated by GCC is 3 times slower than 32 bitGCC 生成的 64 位代码比 32 位慢 3 倍
【发布时间】:2014-10-27 10:55:27
【问题描述】:

我注意到我的代码在 64 位 Linux 上运行比在 32 位 Linux 或 64 位 Window 或 64 位 Mac 上慢得多。这是最小的测试用例。

#include <stdlib.h>

typedef unsigned char UINT8;

void
stretch(UINT8 * lineOut, UINT8 * lineIn, int xsize, float *kk)
{
    int xx, x;

    for (xx = 0; xx < xsize; xx++) {
        float ss = 0.0;
        for (x = 0; x < xsize; x++) {
            ss += lineIn[x] * kk[x];
        }
        lineOut[xx] = (UINT8) ss;
    }
}

int
main( int argc, char** argv )
{
    int i;
    int xsize = 2048;

    UINT8 *lineIn = calloc(xsize, sizeof(UINT8));
    UINT8 *lineOut = calloc(xsize, sizeof(UINT8));
    float *kk = calloc(xsize, sizeof(float));

    for (i = 0; i < 1024; i++) {
        stretch(lineOut, lineIn, xsize, kk);
    }

    return 0;
}

还有它的运行方式:

$ cc --version
cc (Ubuntu 4.8.2-19ubuntu1) 4.8.2
$ cc -O2 -Wall -m64 ./tt.c -o ./tt && time ./tt
user  14.166s
$ cc -O2 -Wall -m32 ./tt.c -o ./tt && time ./tt
user  5.018s

如您所见,32 位版本的运行速度几乎快了 3 倍(我在 32 位和 64 位 Ubuntu 上都进行了测试,结果相同)。更奇怪的是什么性能取决于 C 标准:

$ cc -O2 -Wall -std=c99 -m32 ./tt.c -o ./tt && time ./tt
user  15.825s
$ cc -O2 -Wall -std=gnu99 -m32 ./tt.c -o ./tt && time ./tt
user  5.090s

怎么可能?我该如何解决这个问题以加快 GCC 生成的 64 位版本。

更新 1

我比较了快速 32 位(默认和 gnu99)和慢速(c99)生成的汇编程序,发现如下:

.L5:
  movzbl    (%ebx,%eax), %edx   # MEM[base: lineIn_10(D), index: _72, offset: 0B], D.1543
  movl  %edx, (%esp)    # D.1543,
  fildl (%esp)  #
  fmuls (%esi,%eax,4)   # MEM[base: kk_18(D), index: _72, step: 4, offset: 0B]
  addl  $1, %eax    #, x
  cmpl  %ecx, %eax  # xsize, x
  faddp %st, %st(1) #,
  fstps 12(%esp)    #
  flds  12(%esp)    #
  jne   .L5 #,

在快速情况下没有fstpsflds 命令。因此,GCC 在每一步都从内存中存储和加载值。我试过register float 类型,但这没有帮助。

更新 2

我在 gcc-4.9 上进行了测试,看起来它为 64 位生成了最佳代码。并且-ffast-math(由@jch 建议)修复了两个GCC 版本的-m32 -std=c99。我仍在寻找 gcc-4.8 上 64 位的解决方案,因为它是现在比 4.9 更常见的版本。

【问题讨论】:

  • auto-vectorization 失败的 3 倍提示。看看 -S 程序集清单。
  • @HansPassant 否,“矢量化由标志 -ftree-vectorize 启用,默认为 -O3”。
  • 在没有 gcc 优化的情况下运行程序时,我得到了〜执行时间(用户:0m14.349s(x86)VS 0m14.723s(x86_64))。 GCC 优化失败?!
  • 与问题无关,但为了提供信息:你不能定义一个名为stretch()的函数,所有以str开头的公共函数名称都是保留的。
  • 您是否检查过为 64 位架构编译的 libc6 版本是否用于 64 位版本的编译?

标签: c performance gcc 64-bit


【解决方案1】:

旧版本 GCC 生成的代码中存在部分依赖关系停滞。

movzbl (%rsi,%rax), %r8d
cvtsi2ss %r8d, %xmm0  ;; all upper bits in %xmm0 are false dependency

依赖可以被xorps打破。

#ifdef __SSE__
float __attribute__((always_inline)) i2f(int v) {
    float x;
    __asm__("xorps %0, %0; cvtsi2ss %1, %0" : "=x"(x) : "r"(v) );
    return x;
}
#else
float __attribute__((always_inline)) i2f(int v) { return (float) v; }
#endif

void stretch(UINT8* lineOut, UINT8* lineIn, int xsize, float *kk)
{
    int xx, x;

    for (xx = 0; xx < xsize; xx++) {
        float ss = 0.0;
        for (x = 0; x < xsize; x++) {
            ss += i2f(lineIn[x]) * kk[x];
        }
        lineOut[xx] = (UINT8) ss;
    }
}

结果

$ cc -O2 -Wall -m64 ./test.c -o ./test64 && time ./test64
./test64  4.07s user 0.00s system 99% cpu 4.070 total
$ cc -O2 -Wall -m32 ./test.c -o ./test32 && time ./test32
./test32  3.94s user 0.00s system 99% cpu 3.938 total

【讨论】:

  • 哇,这在我的项目中有效,不仅在测试用例中。唯一的问题是:这段代码如何跨平台? always_inline 可以破解任何编译器吗?编译期间是否所有 x86 64 位目标 CPU 都有 __SSE__ 标志?我还注意到i2f 应该是static
  • @homm 好吧,我想如果你用正确的 ifdefs 来保护它(很遗憾,我无法从我的脑海中告诉你)它将是跨平台的 :) 我希望这已经构建好了在带有 GCC 和 clang 的 Mac/Linux 上很好(即使 clang 并不真正需要这个 hack,它本身就打破了依赖)。对于 MSVC,您需要将 #else-branch 中的 __attribute__ 更改为 __forceinline,否则它应该仍然有效,因为 MSVC not 定义了 __SSE__
  • 如果我将这段代码包含在我的项目中,我可能会这样写#if (GCC_VERSION &lt; 40900) &amp;&amp; defined(__SSE__)
  • 请注意,在具有较旧 gcc 的系统上,可能还有不知道这些指令的汇编程序。我得到了 libImaging/ImagingUtils.h:40: Error: operand type mismatch for 'xorps' libImaging/ImagingUtils.h:40: Error: operand type mismatch for 'cvtsi2ss' 的 py-Pillow 包中的代码,它显然采用了这个技巧。
  • 原始代码包含错误,导致您看到的错误@Rhialto,而不是=X,应该是=x。错误的注册约束。
【解决方案2】:

这是我尝试过的:我将 ss 声明为 volatile。这阻止了编译器对其进行优化。 32 位和 64 位版本的时间相似。

64 位稍慢,但这是正常的,因为 64 位代码较大且 uCode 缓存大小有限。所以一般来说 64bit 应该比 32 慢很多(

回到问题上来,我认为在 32 位模式下,编译器会对 ss 进行更积极的优化。

更新 1:

查看 64 位代码,它会生成一条 CVTTSS2SI 指令,并搭配一条 CVTSI2SS 指令用于浮点到整数的转换。这具有更高的延迟。 32 位代码只使用 FMULS 指令,直接在浮点数上操作。需要寻找一个编译器选项来阻止这些转换。

【讨论】:

  • 我不能声明为int,我需要float
【解决方案3】:

在 32 位模式下,编译器正在努力保持严格的 IEEE 754 浮点语义。您可以通过使用-ffast-math 进行编译来避免这种情况:

$ gcc -m32 -O2 -std=c99 test.c && time ./a.out 

real    0m13.869s
user    0m13.884s
sys     0m0.000s
$ gcc -m32 -O2 -std=c99 -ffast-math test.c && time ./a.out 

real    0m4.477s
user    0m4.480s
sys     0m0.000s

我无法在 64 位模式下重现您的结果,但我非常有信心 -ffast-math 会解决您的问题。更一般地说,除非您真的需要可重现的 IEEE 754 舍入行为,否则 -ffast-math 就是您想要的。

【讨论】:

  • 我想您使用的是 gcc-4.9,看起来它会为 64 位生成快速代码,但对于没有 -ffast-math-std=c99 仍然很慢。在 gcc-4.8 上,快速数学对 -m64 没有帮助。但你的回答仍然是最有帮助的。谢谢!
  • 是的,你是对的。我可以用 gcc-4.8 重现你的结果,事实上,-ffast-math-march=corei7 都不能解决这个问题。
【解决方案4】:

看起来像是限制的情况。三个数组不能重叠吧?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-10-25
    • 1970-01-01
    • 1970-01-01
    • 2013-01-11
    • 2016-06-09
    • 1970-01-01
    • 2015-07-11
    • 2017-02-18
    相关资源
    最近更新 更多