[英]Is float slower than double? Does 64 bit program run faster than 32 bit program?
[英]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 #,
快速情況下沒有fstps
和flds
命令。 所以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.