簡體   English   中英

如何編寫編譯器可以高效編譯為SSE或AVX的c ++代碼?

[英]How to write c++ code that the compiler can efficiently compile to SSE or AVX?

假設我有一個用c ++編寫的函數,它在很多向量上執行矩陣向量乘法。 它需要一個指向要轉換的向量數組的指針。 我是否正確假設編譯器無法有效地優化SIMD指令,因為它在編譯時不知道傳遞指針的對齊(SSE需要16字節對齊或AVX需要32字節對齊)? 或者數據的內存對齊與最佳SIMD代碼無關,數據對齊只會影響緩存性能?

如果對齊對於生成的代碼很重要,我怎么能讓(visual c ++)編譯器知道我打算只傳遞具有特定對齊值的函數?

理論上,自Nehalem以來,對齊英特爾處理器並不重要。 因此,您的編譯器應該能夠生成代碼,其中指針對齊或不對齊不是問題。

自Nehalem以來,未對齊的加載/存儲指令在英特爾處理器上具有相同的性能。 然而,在AVX與Sandy Bridge一起到達之前,未對准的負載無法與另一個微操作融合操作折疊。

此外,即使在AVX之前,為了避免高速緩存行分割具有16字節對齊的內存的懲罰仍然有用,因此編譯器添加代碼直到指針是16字節對齊仍然是合理的。

由於AVX不再使用對齊的加載/存儲指令, 因此編譯器沒有理由添加代碼以使指針對齊16字節或32字節。

但是,有理由使用對齊的內存來避免使用AVX進行緩存行分割。 因此,編譯器添加代碼以使指針32字節對齊是合理的,即使它仍使用未對齊的加載指令。

所以在實踐中,一些編譯器在被告知假設指針對齊時會生成更簡單的代碼。

我不知道告訴MSVC指針是否對齊的方法。 使用GCC和Clang(自3.6起),您可以使用內置的__builtin_assume_aligned 使用ICC和GCC,您可以使用#pragma omp simd aligned 使用ICC,您還可以使用__assume_aligned

例如,GCC編譯這個簡單的循環

void foo(float * __restrict a, float * __restrict b, int n)
{
    //a = (float*)__builtin_assume_aligned (a, 16);
    //b = (float*)__builtin_assume_aligned (b, 16);
    for(int i=0; i<(n & (-4)); i++) {
        b[i] = 3.14159f*a[i];
    }
}

gcc -O3 -march=nehalem -S test.c然后用wc test.s給出160行。 然而,如果使用__builtin_assume_alignedwc test.s僅提供45行。 當我在兩種情況下都這樣做時,clang返回110行。

因此,在clang通知編譯器時,陣列對齊沒有區別(在這種情況下),但與GCC一致。 計算代碼行不是衡量性能的充分指標,但我不打算在這里發布所有程序集我只想說明當編譯器被告知陣列對齊時,編譯器可能會產生非常不同的代碼。

當然,GCC沒有假設陣列對齊的額外開銷可能在實踐中沒有區別。 你必須測試和看到。


無論如何,如果你想從SIMD中獲得最大的收益,我不會依賴編譯器來正確地完成它(特別是對於MSVC)。 你的matrix*vector例子通常很差(但可能不適用於某些特殊情況),因為它的內存帶寬受限。 但是如果你選擇matrix*matrix沒有很多不符合C ++標准的幫助就沒有編譯器能夠很好地優化它。 在這些情況下,您將需要內在函數/內置函數/匯編程序,您可以在其中明確控制對齊。


編輯:

GCC的程序集包含許多無關的行,這些行不屬於文本段。 執行gcc -O3 -march=nehalem -S test.c然后使用objdump -d並對文本(代碼)段中的行進行計數,得到108行,不使用__builtin_assume_aligned ,只使用16行。 這更清楚地表明GCC在假設陣列對齊時產生非常不同的代碼。


編輯:

我繼續在MSVC 2013中測試上面的foo函數。它產生未對齊的加載,代碼比GCC短得多(我只在這里顯示主循環):

$LL3@foo:
    movsxd  rax, r9d
    vmulps  xmm1, xmm0, XMMWORD PTR [r10+rax*4]
    vmovups XMMWORD PTR [r11+rax*4], xmm1
    lea eax, DWORD PTR [r9+4]
    add r9d, 8
    movsxd  rcx, eax
    vmulps  xmm1, xmm0, XMMWORD PTR [r10+rcx*4]
    vmovups XMMWORD PTR [r11+rcx*4], xmm1
    cmp r9d, edx
    jl  SHORT $LL3@foo

自Nehalem(2008年末)以來,處理器應該沒問題。 但MSVC仍然擁有不是4的倍數的數組的清理代碼,即使我告訴編譯器它是4的倍數( (n & (-4) -4 (n & (-4) )。至少GCC是正確的。


由於AVX可以折疊未打開的負載,我用AVX檢查GCC以查看代碼是否相同。

void foo(float * __restrict a, float * __restrict b, int n)
{
    //a = (float*)__builtin_assume_aligned (a, 32);
    //b = (float*)__builtin_assume_aligned (b, 32);
    for(int i=0; i<(n & (-8)); i++) {
        b[i] = 3.14159f*a[i];
    }
}

如果沒有__builtin_assume_aligned GCC會產生168條裝配線,並且它只生產17條生產線。

我的原始答案變得太亂了,無法編輯,所以我在這里添加一個新的答案並制作我的原始答案社區維基。

我在前Nehalem系統和帶有GCC,Clang和MSVC的Haswell系統上使用對齊和未對齊的內存進行了一些測試。

程序集顯示只有GCC添加代碼來檢查和修復對齊。 由於這與__builtin_assume_aligned GCC產生了更簡單的代碼。 但是將__builtin_assume_aligned與Clang一起使用只會將未對齊的指令更改為對齊(指令數保持不變)。 MSVC只使用未對齊的指令。

性能的結果是,對於每個Nehalem系統,當內存未對齊時,Clang和MSVC比具有自動矢量化的GCC慢得多。

但是,自Nehalem以來,對緩存線分割的懲罰很小。 事實證明,GCC增加的額外代碼用於檢查和對齊內存,而不是彌補由於緩存線分割造成的小額懲罰。 這解釋了為什么Clang和MSVC都不擔心帶有矢量化的緩存行分裂。

因此,自從Nehalem以來,我最初聲稱自動化不需要了解對齊方式或多或少是正確的。 這與說Nehalem以來對齊記憶無用是不一樣的。

暫無
暫無

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

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