繁体   English   中英

GCC生成的64位代码比32位慢3倍

[英]64 bit code generated by GCC is 3 times slower than 32 bit

我注意到我的代码在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。

旧版本的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

这是我尝试过的:我宣称ss是易变的 这阻止了编译器对其进行优化。 我得到了32位和64位版本的类似时间。

64位稍慢,但这是正常的,因为64位代码更大,uCode缓存的大小有限。 所以一般来说64位应该比32(<3-4%)慢一点。

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

更新1:

查看64位代码,它会生成CVTTSS2SI指令,并与CVTSI2SS指令配对,以进行浮点到整数转换。 这具有更高的延迟。 32位代码只使用FMULS指令,直接在浮点数上运行。 需要查找编译器选项以防止这些转换。

在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就是你想要的。

看起来像限制的情况。 三个阵列不能重叠,可以吗?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM