2011-10-27 78 views
13

這篇文章與我發佈some days ago的另一篇文章密切相關。這一次,我寫了一個簡單的代碼,它只添加一對元素數組,將結果乘以另一個數組中的值,並將其存儲在第四個數組中,所有變量均爲浮點雙精度類型。GCC SSE代碼優化

我做了代碼的兩個版本:一個使用SSE指令,用來調用和另一個沒有他們,我然後用gcc和-O0優化級別編譯它們。我把它們寫在下面:

// SSE VERSION 

#define N 10000 
#define NTIMES 100000 
#include <time.h> 
#include <stdio.h> 
#include <xmmintrin.h> 
#include <pmmintrin.h> 

double a[N] __attribute__((aligned(16))); 
double b[N] __attribute__((aligned(16))); 
double c[N] __attribute__((aligned(16))); 
double r[N] __attribute__((aligned(16))); 

int main(void){ 
    int i, times; 
    for(times = 0; times < NTIMES; times++){ 
    for(i = 0; i <N; i+= 2){ 
     __m128d mm_a = _mm_load_pd(&a[i]); 
     _mm_prefetch(&a[i+4], _MM_HINT_T0); 
     __m128d mm_b = _mm_load_pd(&b[i]); 
     _mm_prefetch(&b[i+4] , _MM_HINT_T0); 
     __m128d mm_c = _mm_load_pd(&c[i]); 
     _mm_prefetch(&c[i+4] , _MM_HINT_T0); 
     __m128d mm_r; 
     mm_r = _mm_add_pd(mm_a, mm_b); 
     mm_a = _mm_mul_pd(mm_r , mm_c); 
     _mm_store_pd(&r[i], mm_a); 
     } 
    } 
} 

//NO SSE VERSION 
//same definitions as before 
int main(void){ 
    int i, times; 
    for(times = 0; times < NTIMES; times++){ 
    for(i = 0; i < N; i++){ 
     r[i] = (a[i]+b[i])*c[i]; 
    } 
    } 
} 

當-O0編譯它們,GCC利用的XMM/MMX寄存器和SSE intstructions,如果沒有具體給出的-mno-SSE(及其他)選項。我檢查了爲第二個代碼生成的彙編代碼,並且我注意到它使用了 movsd, addsd mulsd說明。所以它使用SSE指令,但只有那些使用寄存器最低部分的指令,如果我沒有錯的話。用於第一C代碼生成的彙編代碼利用,正如所料, ADDP mulpd指令的,儘管產生一個相當大的彙編代碼。

不管怎麼說,第一個代碼應該得到更好的利潤,據我所知,SIMD範式,因爲每次迭代兩個結果值計算。儘管如此,第二個代碼的執行速度比第一個快25%。我還用單精度值進行了測試,得到了類似的結果。這是什麼原因?

+5

沒有優化編譯時對比效果是毫無意義的。 – interjay

+1

您只需進行3次x加載和1次存儲即可進行2次x運算操作,因此您很可能會受到帶寬限制。 –

+5

當您刪除_mm_prefetch調用時會發生什麼?我認爲他們可能會傷害你 – TJD

回答

14

GCC中的向量化啓用在-O3。這就是爲什麼在-O0,你只能看到普通的標量SSE2指令(movsd,addsd等)。使用GCC 4.6.1和你的第二個例子:

#define N 10000 
#define NTIMES 100000 

double a[N] __attribute__ ((aligned (16))); 
double b[N] __attribute__ ((aligned (16))); 
double c[N] __attribute__ ((aligned (16))); 
double r[N] __attribute__ ((aligned (16))); 

int 
main (void) 
{ 
    int i, times; 
    for (times = 0; times < NTIMES; times++) 
    { 
     for (i = 0; i < N; ++i) 
     r[i] = (a[i] + b[i]) * c[i]; 
    } 

    return 0; 
} 

gcc -S -O3 -msse2 sse.c編譯產生的內環下面的說明,這是非常好的:

.L3: 
    movapd a(%eax), %xmm0 
    addpd b(%eax), %xmm0 
    mulpd c(%eax), %xmm0 
    movapd %xmm0, r(%eax) 
    addl $16, %eax 
    cmpl $80000, %eax 
    jne .L3 

正如你所看到的,與矢量啓用GCC發出代碼並行執行兩個循環迭代。它可以改善,雖然 - 此代碼使用SSE的下部128位寄存器,但它可以通過啓用的SSE指令的AVX編碼(如果可用的計算機上)使用完整的256位寄存器YMM,。所以,用gcc -S -O3 -msse2 -mavx sse.c編譯相同的程序給出了內環:

.L3: 
    vmovapd a(%eax), %ymm0 
    vaddpd b(%eax), %ymm0, %ymm0 
    vmulpd c(%eax), %ymm0, %ymm0 
    vmovapd %ymm0, r(%eax) 
    addl $32, %eax 
    cmpl $80000, %eax 
    jne .L3 

注意,在每個指令的前面和v指令使用256位YMM寄存器,原始循環的迭代執行在平行下。

+0

我剛剛在'x86-64'上運行'gcc 4.7.2'並且沒有'-msse2'標誌 - 兩者都產生了相同的彙編輸出。那麼在這個平臺上默認啓用指令會很安全嗎? –

+0

@lori,是的,SSE在x86-64上是默認的。 – chill

+0

在這裏,你可以用不同的編譯器檢查http://goo.gl/bM62CZ – KindDragon

2

我想擴展chill's answer,並提請您注意GCC似乎無法在反向迭代時同樣聰明地使用AVX指令。

for (i = N-1; i >= 0; --i) 
    r[i] = (a[i] + b[i]) * c[i]; 

GCC(4.8:

只是替換寒意的示例代碼內循環。4)選項-S -O3 -mavx生產:

.L5: 
    vmovsd a+79992(%rax), %xmm0 
    subq $8, %rax 
    vaddsd b+80000(%rax), %xmm0, %xmm0 
    vmulsd c+80000(%rax), %xmm0, %xmm0 
    vmovsd %xmm0, r+80000(%rax) 
    cmpq $-80000, %rax 
    jne  .L5 
+0

有趣。較新的gcc自動矢量化,對每個數組輸入/輸出使用'vpermpd 0b00011011'來自動進行矢量化,以便在加載後將其反轉,以便每個矢量中的數據元素按照源順序從第一個到最後一個。這是每次迭代4次'vpermpd'!有趣的是,[clang自動將它很好地矢量化](https://godbolt.org/g/azbIIi) –