2015-02-23 141 views
2

你好的StackOverflow社區在SSE寄存器(GCC,C++)

我也遇到以下挑戰存儲一個常數:在我的C++應用程序,我在其中相當複雜(立方)循環,在所有的深處,我執行以下:

  1. 計算4浮子由恆定
  2. 乘所有4個值轉換浮子爲整數

該代碼將在每個循環中執行數千次迭代(導致數十億次操作),並且我希望儘可能快地實現它,所以我試圖利用SSE處理器指令。

在嘗試手動優化代碼時,我遇到了以下障礙:每當我將所有值乘以一個常量時,都必須將常量加載到XMM寄存器。我的想法是保留一個寄存器(並禁止編譯器使用它),加載一次值,並用一個特定寄存器對乘法進行硬編碼,但是我找不到正確的方法來實現這一點。

順便說一句,可能有人請向我解釋,爲什麼這個代碼:

vmovaps .LC0(%rip), %xmm1 
    movl $1000000000, %eax 
    vmovaps .LC1(%rip), %xmm0 
    .p2align 4,,10 
    .p2align 3 
.L2: 
#APP 
# 26 "sse.cpp" 1 
    .intel_syntax noprefix; 
    mulps %xmm1,%xmm0; 
    .att_syntax prefix; 

# 0 "" 2 
#NO_APP 
    subl $1, %eax 
    jne  .L2 

執行得很差(實0m1.656s vs皇家0m1.618s)比下面的例子:

vmovaps .LC0(%rip), %xmm1 
    movl $1000000000, %eax 
    vmovaps .LC1(%rip), %xmm0 
    .p2align 4,,10 
    .p2align 3 
.L2: 
    vmulps %xmm0, %xmm1, %xmm1 
    subl $1, %eax 
    jne  .L2 

(區別在於我在gcc [第一段代碼]和傳統SSE指令中使用intel語法來兼容,而gcc使用AVX向量[第二代碼段]自動生成版本)

+2

您應該使用內置向量支持和/或內在函數。另外,如果你想用-masm = intel的intel語法進行編譯,不要試圖繞過編譯器的後面。至於保留一個寄存器,這也可能是一個壞主意,但gcc允許全局寄存器變量。 – Jester 2015-02-23 17:20:24

+0

如http://stackoverflow.com/a/9080351/1133179幫助中所述,Umm沒有聲明'const __mm128'變量? – luk32 2015-02-23 17:22:00

+0

@Jester -masm = intel打破了一些提升依賴關係,試過之前。 – Marandil 2015-02-23 21:38:47

回答

2

需要注意的是,您需要更具體地瞭解您如何編譯內容,並可能提供最少的示例。我知道這可能不是最好的答案,但我認爲它夠好。它變長了,但這是因爲代碼。

下面的工作的底線是它應該是安全的離開編譯器並使用適當的編譯器標誌。在底部,我舉了一個例子說明如何使用本地寄存器變量,但它可能不會很有用(它很容易被忽略)。你可以使用全局寄存器變量,但它不會產生任何好結果,並且不鼓勵。

我的設置是Intel(R) Core(TM) i7-4770 CPU,gcc version 4.9.2clang version 3.5.0。以下代碼確實將avx_scalar存儲在xmm寄存器中,其中-O1及以上。沒有什麼或-O0他們沒有。要生成彙編代碼爲:

[clang++|g++] -march=native -S -Ox ./sse.cpp

其中x是優化級別。

有趣的是,與-march=archive這兩個編譯器決定使用SSE4.1版本超過傳統SSE在任何情況下我測試,即使我在代碼本身中使用傳統的SSE內在函數。這很好。

我還測試了使用smmintrin.h這是SSE4.1頭。沒有國旗gcc使用傳統的SSE和鐺無法與error: "SSE4.1 instruction set not enabled"編譯。通過xmmintrin.h這是傳統的SSE頭文件,這兩個編譯器在出現標誌的情況下生成AVX版本,而在遺漏的時候編譯器生成AVX版本。

測試代碼avx.cpp

Revelvant部分g++ -march=native -S -O2 ./avx.cpp

extern "C" 
{ 
#include <smmintrin.h> 
} 

const float scalar = 3.14; 
const __m128 avx_scalar = _mm_set1_ps(scalar); 
__m128 vector; 

__m128 its_me(){ 
    __m128 ret; 
    __m128 result; 
    for(int i = 0; i < 1000; ++i) 
    { 
     vector = _mm_set_ps(i*1,i*2,i*3,i*4); 
     result = _mm_mul_ps(vector, avx_scalar); 
     ret = _mm_add_ps(ret, result); 
    } 
    return ret; 
} 

.LFB639: 
     .cfi_startproc 
     vmovaps _ZL10avx_scalar(%rip), %xmm5 
     xorl %edx, %edx 
     .p2align 4,,10 
     .p2align 3 
.L2: 
     leal (%rdx,%rdx), %ecx 
     vxorps %xmm2, %xmm2, %xmm2 
     vxorps %xmm1, %xmm1, %xmm1 
     vxorps %xmm3, %xmm3, %xmm3 
     leal 0(,%rdx,4), %eax 
     vcvtsi2ss  %ecx, %xmm3, %xmm3 
     vxorps %xmm4, %xmm4, %xmm4 
     vcvtsi2ss  %eax, %xmm2, %xmm2 
     leal (%rcx,%rdx), %eax 
     vcvtsi2ss  %edx, %xmm4, %xmm4 
     addl $1, %edx 
     vcvtsi2ss  %eax, %xmm1, %xmm1 
     vunpcklps  %xmm4, %xmm3, %xmm3 
     vunpcklps  %xmm1, %xmm2, %xmm1 
     vmovlhps  %xmm3, %xmm1, %xmm1 
     vmulps %xmm5, %xmm1, %xmm2 
     vaddps %xmm2, %xmm0, %xmm0 
     cmpl $1000, %edx 
     jne  .L2 
     vmovaps %xmm1, vector(%rip) 
     ret 
     .cfi_endproc 

而且clang++ -march=native -S -O2 ./avx.cpp

# BB#0: 
     xorl %eax, %eax 
     movl $4, %ecx 
     movl $2, %edx 
     vmovaps _ZL10avx_scalar(%rip), %xmm1 
     xorl %esi, %esi 
             # implicit-def: XMM0 
     .align 16, 0x90 
.LBB0_1:        # =>This Inner Loop Header: Depth=1 
     leal -2(%rdx), %r8d 
     leal -4(%rcx), %edi 
     vmovd %edi, %xmm2 
     vpinsrd $1, %eax, %xmm2, %xmm2 
     vpinsrd $2, %r8d, %xmm2, %xmm2 
     vpinsrd $3, %esi, %xmm2, %xmm2 
     vcvtdq2ps  %xmm2, %xmm2 
     vmulps %xmm1, %xmm2, %xmm2 
     vaddps %xmm2, %xmm0, %xmm0 
     leal 1(%rsi), %r8d 
     leal 3(%rax), %edi 
     vmovd %ecx, %xmm2 
     vpinsrd $1, %edi, %xmm2, %xmm2 
     vpinsrd $2, %edx, %xmm2, %xmm2 
     vpinsrd $3, %r8d, %xmm2, %xmm2 
     vcvtdq2ps  %xmm2, %xmm2 
     vmulps %xmm1, %xmm2, %xmm3 
     vaddps %xmm3, %xmm0, %xmm0 
     addl $2, %esi 
     addl $6, %eax 
     addl $8, %ecx 
     addl $4, %edx 
     cmpl $1000, %esi    # imm = 0x3E8 
     jne  .LBB0_1 
# BB#2: 
     vmovaps %xmm2, vector(%rip) 
     retq 

爲了記錄,您可以手動把本地變量到寄存器,但鐺完全忽略與-01和above.I GCC鼓勵尋找xmm13在輸出g++ -march=native -S -Ox ./avx.cpp與下面的代碼不同x值(假設你有你的CPU至少13個XMM寄存器):

extern "C" 
{ 
#include <xmmintrin.h> 
} 

const float scalar = 3.14; 

__m128 its_me(){ 
    __m128 vector; 
    register __m128 avx_scalar asm ("xmm13") = _mm_set1_ps(scalar); // that's how you do it in gcc. 
    //const __m128 avx_scalar = _mm_set1_ps(scalar); 
    __m128 ret; 
    __m128 result; 
    for(int i = 0; i < 1000; ++i) 
    { 
     vector = _mm_set_ps(i*1,i*2,i*3,i*4); 
      result = _mm_mul_ps(vector, avx_scalar); 
     ret = _mm_add_ps(ret, result); 
    } 
    return ret; 
} 
+0

如果可以避免,請不要在內部循環中使用_mm_set_ps。設置「{0.0,0.0,0.0,0.0}」一次,然後設置「_mm_add_ps」爲{{1.0,2.0,3.0,4.0}}的向量來生成vector。這將在循環中用「vaddps」替換4個整數加法,'vmovd',3x'vpinsrd'和'vcvtdq2ps'。或者,如果您確實需要避免任何可能的舍入誤差累積,請將其添加到整數向量中,以便在內部循環中只有「vpaddd」和「vcvtdq2ps」。 – 2015-07-10 18:57:37

+0

這是一個很好的分析,儘管對於答案來說很沒用。 '__mm_set_ps'只是爲了模擬「* Compute 4 float values *」,它可能很慢,而且它不是一個真正的實現。我真的沒有看到優化組成部分的一點。儘管如此,我仍然從微觀優化的角度發現了一個有趣的觀察和建議。它當然有其價值。 – luk32 2015-07-12 18:37:29

+0

我想我沒有仔細閱讀代碼之外的文本,以便在循環計數器上佔用'mm_set_ps'作爲佔位符。實際上,我似乎還記得這個想法,但它對生成的代碼有很大的影響,所以我仍然發佈。 >< – 2015-07-12 19:20:26