簡體   English   中英

編譯器是否對常規 C 代碼使用 SSE 指令?

[英]Does compiler use SSE instructions for a regular C code?

我看到人們默認使用-msse -msse2 -mfpmath=sse標志,希望這會提高性能。 我知道當在 C 代碼中使用特殊向量類型時,SSE 會參與進來。 但是這些標志對常規 C 代碼有什么影響嗎? 編譯器是否使用 SSE 優化常規 C 代碼?

是的,如果使用完全優化進行編譯,現代編譯器將使用SSE2進行自動向量化。 clang即使在-O2,gcc at -O3也可以使用它。

即使在-O1或-Os,編譯器也將使用SIMD加載/存儲指令來復制或初始化比整數寄存器更寬的結構或其他對象。 這並不算作自動矢量化; 對於小型固定大小的塊,它更像是默認內置memset / memcpy策略的一部分。 但它確實利用並要求支持SIMD指令。


SSE2對於x86-64是基線/非可選的,因此編譯器在定位x86-64時始終可以使用SSE1 / SSE2指令 必須手動啟用后面的指令集(SSE4,AVX,AVX2,AVX512和非SIMD擴展,如BMI2,popcnt等),告訴編譯器可以編寫不能在舊CPU上運行的代碼。 或者讓它生成多個版本的代碼並在運行時選擇,但這會帶來額外的開銷,並且僅值得用於更大的功能。

-msse -msse2 -mfpmath=sse已經是x86-64的默認值 ,但不適用於32位i386。 一些32位調用約定在x87寄存器中返回FP值,因此使用SSE / SSE2進行計算可能不方便,然后必須存儲/重新加載結果以使其在x87 st(0) 使用-mfpmath=sse ,更智能的編譯器可能仍然使用x87進行產生FP返回值的計算。

在32位x86上,默認情況下-msse2可能不會打開,這取決於編譯器的配置方式。 如果你使用的是32位,因為你的目標是那么老的CPU 無法運行64位代碼,你可能需要確保它被禁用,或者只是-msse

對正在編譯的CPU進行二進制調整的最佳方法是-O3 -march=native -mfpmath=sse ,並使用鏈接時優化+配置文件引導優化 (gcc -fprofile-generate / run on some test data / gcc -fprofile-use )。

如果編譯器確實選擇使用新指令,則使用-march=native會生成可能無法在早期CPU上運行的二進制文件。 配置文件引導優化對gcc非常有用:它永遠不會在沒有它的情況下展開循環。 但是對於PGO,它知道哪些循環經常運行/進行大量迭代,即哪些循環“熱”並且值得花費更多的代碼大小。 鏈接時優化允許跨文件進行內聯/常量傳播。 如果您的C ++具有很多小函數,而這些函數實際上並未在頭文件中定義,那將非常有用。


請參閱如何從GCC /鏗鏘聲組件輸出中刪除“噪音”? 有關查看編譯器輸出和理解它的更多信息。

以下是關於x86-64 的Godbolt編譯器資源管理器的一些具體示例 Godbolt還有其他幾種架構的gcc,並且你可以添加-target mips或其他任何內容,因此你也可以看到ARM NEON的自動矢量化,並使用正確的編譯器選項來啟用它。 您可以將-m32與x86-64編譯器一起使用以獲得32位代碼。

int sumint(int *arr) {
    int sum = 0;
    for (int i=0 ; i<2048 ; i++){
        sum += arr[i];
    }
    return sum;
}

內循環與gcc8.1 -O3 (沒有-march=haswell或任何啟用AVX / AVX2的東西):

.L2:                                 # do {
    movdqu  xmm2, XMMWORD PTR [rdi]    # load 16 bytes
    add     rdi, 16
    paddd   xmm0, xmm2                 # packed add of 4 x 32-bit integers
    cmp     rax, rdi
    jne     .L2                      # } while(p != endp)

    # then horizontal add and extract a single 32-bit sum

如果沒有-ffast-math ,編譯器無法重新排序FP操作,因此float等效函數不會自動向量化(參見Godbolt鏈接:你得到標量addss )。 (OpenMP可以基於每個循環啟用它,或使用-ffast-math )。

但是一些FP的東西可以安全地自動矢量化而不改變操作順序。

// clang won't contract this into an FMA without -ffast-math :/
// but gcc will (if you compile with -march=haswell)
void scale_array(float *arr) {
    for (int i=0 ; i<2048 ; i++){
        arr[i] = arr[i] * 2.1f + 1.234f;
    }
}

  # load constants: xmm2 = {2.1,  2.1,  2.1,  2.1}
  #                 xmm1 = (1.23, 1.23, 1.23, 1.23}
.L9:   # gcc8.1 -O3                       # do {
    movups  xmm0, XMMWORD PTR [rdi]         # load unaligned packed floats
    add     rdi, 16
    mulps   xmm0, xmm2                      # multiply Packed Single-precision
    addps   xmm0, xmm1                      # add Packed Single-precision
    movups  XMMWORD PTR [rdi-16], xmm0      # store back to the array
    cmp     rax, rdi
    jne     .L9                           # }while(p != endp)

乘數= 2.0f導致使用addps加倍,在Haswell / Broadwell上將吞吐量降低2倍! 因為在SKL之前,FP add僅在一個執行端口上運行,但有兩個FMA單元可以運行乘法。 SKL放棄了加法器,運行時添加了與每個時鍾吞吐量和延遲相同的2個mul和FMA。 http://agner.org/optimize/ ,並查看x86標簽wiki其他性能鏈接。)

使用-march=haswell進行編譯可讓編譯器使用單個FMA進行縮放+添加。 (但是除非你使用-ffast-math否則clang不會將表達式收縮到FMA中-ffast-math可以選擇在沒有其他激進操作的情況下啟用FP收縮。)

一般來說,這是不可能的。 但是,對於某些特定的C源代碼和編譯器,您可以通過查看生成的程序集來回答這個問題。 幾乎所有編譯器都應該有一個創建匯編文件的選項。 然后,您可以搜索SSE指令。

對於大多數Unix C編譯器,請使用-S選項。 有關詳細信息請閱讀編譯器的精細手冊

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM