2012-06-14 19 views
9

假設我想添加兩個緩衝區並存儲結果。兩個緩衝區已分配16byte對齊。我發現了兩個例子如何做到這一點。SSE:_mm_load/store與使用直接指針訪問之間的區別

第一個是使用_mm_load從緩衝區讀取數據到一個SSE寄存器,執行添加操作並將其存儲回結果寄存器。直到現在,我會這樣做。

void _add(uint16_t * dst, uint16_t const * src, size_t n) 
{ 
    for(uint16_t const * end(dst + n); dst != end; dst+=8, src+=8) 
    { 
    __m128i _s = _mm_load_si128((__m128i*) src); 
    __m128i _d = _mm_load_si128((__m128i*) dst); 

    _d = _mm_add_epi16(_d, _s); 

    _mm_store_si128((__m128i*) dst, _d); 
    } 
} 

第二個例子只是在沒有加載/存儲操作的情況下直接在內存地址上進行加操作。兩條縫工作正常。

void _add(uint16_t * dst, uint16_t const * src, size_t n) 
{ 
    for(uint16_t const * end(dst + n); dst != end; dst+=8, src+=8) 
    { 
    *(__m128i*) dst = _mm_add_epi16(*(__m128i*) dst, *(__m128i*) src); 
    } 
} 

所以,問題是如果該第二示例是正確的,或者可以具有任何副作用以及何時使用加載/存儲是強制性的。

謝謝。

+0

有沒有人知道任何「官方」文件在深層解釋這一點?我使用了「英特爾®C++內部函數參考」,但發現它不能清楚地回答我的問題。 – Peter

+0

'load' /'loadu'內在函數的主要目的是將對齊信息傳遞給編譯器。並且(對於float/double),可以從'float *'輸入到'__m128'或'double *'到'__m128d'。對於整數,你必須自己施放。 (但固定AVX512,其中整數加載/存儲內部函數採取'void *'參數) –

回答

10

兩個版本都很好 - 如果你看一下生成的代碼,你會看到第二個版本仍然會產生至少一個負載向量寄存器,因爲PADDW(又名_mm_add_epi16)只能直接從內存中獲取它的第二個參數。

在實踐中,大多數非平凡的SIMD代碼將在加載和存儲數據之間進行更多操作,而不僅僅是一次添加,所以一般來說,您可能希望使用_mm_load_XXX將數據初始加載到向量變量(寄存器) SIMD在寄存器上的操作,然後通過_mm_store_XXX將結果存回內存。

+0

所以,你說什麼基本上是如果我會有更多的操作,將重用我可以保存在第一個例子中的_d/_s變量和否則沒有區別? – Peter

+0

是的 - 就是這麼多 - 加載和存儲理想情況下應該是SIMD循環中相對較小的一部分(否則你很可能會受到內存帶寬的限制而不是計算限制),所以它也無關緊要。從內存中獲取SIMD寄存器並再次返回。 –

+0

@PaulR是否正確,如果你使用load,然後改變創建的變量source不會改變,並且如果你使用指針並且做出改變,source會改變嗎? – Martinsos

4

主要區別在於,如果第二個版本不能證明指針是16字節對齊的,那麼編譯器將生成未對齊的加載(movdqu等)。根據周圍的代碼,編譯器可以證明該屬性可以被證明的地方甚至不可能編寫代碼。

否則沒有區別,編譯器足夠聰明,可以將兩個加載和加載到一個加載和一個加載內存(如果它認爲有用)或將加載和加載指令拆分爲兩個。

如果使用C++,也可以寫

void _add(__v8hi* dst, __v8hi const * src, size_t n) 
{ 
    n /= 8; 
    for(int i=0; i<n; ++i) 
     d[i| += s[i]; 
} 

__v8hi爲的8個半整數或typedef short __v8hi __attribute__ ((__vector_size__ (16)));矢量的縮寫,也有類似的預定義類型的每個矢量型,由兩者的gcc支持和icc。

這將導致幾乎相同的代碼,可能會更快,也可能不會更快。但有人可能會認爲它更具可讀性,可以很容易地擴展到AVX,甚至可以通過編譯器。

+1

我從來沒有見過編譯器爲該類型的轉換生成未對齊的加載。即使數據類型(有意)未對齊也是如此。當然,它運行時會崩潰。 – Mysticial

+0

我不止一次地遇到過這種情況。 AFAIR一些工會和鑄造參與。 – hirschhornsalz

+0

我查看了我的代碼的程序集,發現沒有MOVDQU指令。一切都編譯到MOVDQA,所以它接縫沒問題。 – Peter

1

至少用gcc/clang,foo = *dst;foo = _mm_load_si128(dst);完全一樣。 _mm_load_si128的方式通常是慣例,但對齊的__m128i*的簡單C/C++解引用也是安全的。


load/loadu內在的主要目的是向對準信息傳送給編譯器。

對於float /雙,他們還類型轉換(constfloat*__m128或(constdouble* <之間 - >__m128d。對於整數,你還是要投自己:(。但是,這固定AVX512內部函數,其中整數加載/存儲內在需要void* ARGS。

編譯器仍然可以優化掉死商店或重新加載,並摺疊加載到內存中的操作數對於ALU指令,但是當它們實際上在它們的組件輸出中發射存儲或加載時,它們以一種在給定源中的對齊保證(或缺少對齊)的情況下不會產生錯誤的方式來執行它。讓編譯器將載入內容的操作數加載到帶有SSE或AVX的ALU指令的內存操作數中,但未對齊的加載內部函數只能與AVX一起摺疊,因爲SSE內存操作數與movdqa加載類似。例如_mm_add_epi16(xmm0, _mm_loadu_si128(rax))可編譯t Øvpaddw xmm0, xmm0, [rax]與AVX,但與SSE將不得不編譯到movdqu xmm1, [rax]/paddw xmm0, xmm1。 A load而不是loadu可以讓它避免與SSE分開的加載指令。


由於是正常的C,解引用__m128i*被假定爲對齊的訪問,如load_si128store_si128

在gcc的emmintrin.h中,__m128i類型定義爲__attribute__ ((__vector_size__ (16), __may_alias__))

如果它使用了__attribute__ ((__vector_size__ (16), __may_alias__, aligned(1))),那麼gcc會將取消引用視爲未對齊的訪問。

+0

感謝您的回答,但它非常詳細,如果我明白了我的意思,我不會確定。你說這兩個版本都可以編譯好,但是如果我不使用load,編譯器不能決定對齊方式,並且總是會假定未對齊的內存? – Peter

+0

@Peter:'foo = * dst'與'foo = _mm_load_si128(dst)'完全相同。取消引用'__m128i'時的「默認」是一個可能在未對齊時出錯的訪問。 –

相關問題