2011-11-19 45 views
11

我想乘以SSE4一個__m128i對象與16位無符號8位整數,但我只能找到一個內在乘法16位整數。有沒有什麼比如_mm_mult_epi8SSE乘法16 x uint8_t

+1

你能澄清一下你的問題嗎?您是否要將每個16位8位整數的128位整數或16位8位整數的16位整數或16位8位整數乘以一個寄存器來相乘。前一種情況會有點奇怪。 –

+0

只是一個想法,但爲什麼不把8位到16位?如果你想測試溢出,你可以將AH與AH進行比較,看看是否匹配檢查溢出。有點凌亂,只是在黑暗中刺傷。如果直接支持8位mul,也會讓我感到驚訝,因爲SIMD的指令集是爲後8位處理器編寫的 –

+0

@Paul:8位值仍在圖形中使用。 AltiVec具有8位乘法,但每次只有8位乘法結果爲16位。 – Potatoswatter

回答

11

MMX/SSE/AVX中沒有8位乘法。但是,您可以模擬使用16位乘法8位乘法內在如下:

inline __m128i _mm_mullo_epi8(__m128i a, __m128i b) 
{ 
    __m128i zero = _mm_setzero_si128(); 
    __m128i Alo = _mm_cvtepu8_epi16(a); 
    __m128i Ahi = _mm_unpackhi_epi8(a, zero); 
    __m128i Blo = _mm_cvtepu8_epi16(b); 
    __m128i Bhi = _mm_unpackhi_epi8(b, zero); 
    __m128i Clo = _mm_mullo_epi16(Alo, Blo); 
    __m128i Chi = _mm_mullo_epi16(Ahi, Bhi); 
    __m128i maskLo = _mm_set_epi8(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 14, 12, 10, 8, 6, 4, 2, 0); 
    __m128i maskHi = _mm_set_epi8(14, 12, 10, 8, 6, 4, 2, 0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80); 
    __m128i C = _mm_or_si128(_mm_shuffle_epi8(Clo, maskLo), _mm_shuffle_epi8(Chi, maskHi)); 

    return C; 
} 
8

唯一的8位SSE乘法指令是PMADDUBSW(SSSE3及更高版本,C/C++內部函數:_mm_maddubs_epi16)。這將16 x 8位無符號值乘以16 x 8位帶符號的值,然後求和相鄰對以給出8 x 16位帶符號結果。如果你不能使用這個相當專業的指令,那麼你需要解壓縮成16位向量對,並使用常規的16位乘法指令。顯然這意味着至少有2倍的吞吐量,所以如果可能的話使用8位乘法。

12

一個基於Agner Fog's solution不是薩芬的解決方案(可能)更快捷的方式:

而不是分裂高/低,分裂奇偶。這有一個額外的好處,它可以在純SSE2中工作,而不需要SSE4.1(對於OP來說沒有用處,但是對於一些用戶來說是一個不錯的附加獎勵)。如果您有AVX2,我還添加了一項優化。從技術上講,AVX2優化僅適用於SSE2內在函數,但比左右移位要慢。

__m128i mullo_epi8(__m128i a, __m128i b) 
{ 
    // unpack and multiply 
    __m128i dst_even = _mm_mullo_epi16(a, b); 
    __m128i dst_odd = _mm_mullo_epi16(_mm_srli_epi16(a, 8),_mm_srli_epi16(b, 8)); 
    // repack 
#ifdef __AVX2__ 
    // only faster if have access to VPBROADCASTW 
    return _mm_or_si128(_mm_slli_epi16(dst_odd, 8), _mm_and_si128(dst_even, _mm_set1_epi16(0xFF))); 
#else 
    return _mm_or_si128(_mm_slli_epi16(dst_odd, 8), _mm_srli_epi16(_mm_slli_epi16(dst_even,8), 8)); 
#endif 
} 

Agner使用blendv_epi8固有的SSE4.1支持。

編輯:

有趣的是,做更多的工作分解(具有優化的版本)後,至少我的兩個實現被編譯到完全一樣的東西。以「ivy-bridge」(AVX)爲目標的示例反彙編。

vpmullw xmm2,xmm0,xmm1 
vpsrlw xmm0,xmm0,0x8 
vpsrlw xmm1,xmm1,0x8 
vpmullw xmm0,xmm0,xmm1 
vpsllw xmm0,xmm0,0x8 
vpand xmm1,xmm2,XMMWORD PTR [rip+0x281] 
vpor xmm0,xmm0,xmm1 

它使用預編譯的128位xmm常數的「AVX2優化」版本。僅編譯SSE2支持會產生類似的結果(儘管使用SSE2指令)。我懷疑Agner Fog的原始解決方案可能會得到相同的優化(如果沒有的話,會很瘋狂)。不知道Marat的原始解決方案是如何在優化構建中進行比較的,儘管對於所有x86 simd擴展包含比SSE2更新並且包含SSE2的方法,這是相當不錯的。

+2

這真的很好。它利用了有符號與無符號隻影響N×N - > 2N位乘法的高一半的事實,並且[高位中的垃圾不會影響你想要的低位結果]( http://stackoverflow.com/questions/34377711/which-2s-complement-integer-operations-can-be-used-without-zeroing-high-bits-in)。如果在加載掩碼時緩存未命中是一個問題,您可以使用2個insns產生它:'pcmpeqw xmm7,xmm7' /'psrlw xmm7,8'。 (有關其他常量生成序列,請參閱http://stackoverflow.com/q/35085059/224132)。 –

+1

這很整潔,我看到[clang優化左移/右移到帶有常量掩碼的vpand](http://goo.gl/GmFc9H)!這可能是更好的代碼,除非掩碼容易在緩存中丟失。 gcc不會做這種優化。 shift和mask之間的選擇完全不依賴於AVX2。它取決於內存中的一個大常量是否是你想要的。 (我注意到沒有avx,clang最後浪費了一個movdqa:它可能在第二個pmul使用了'pmullw xmm0,xmm1',並在'xmm0'(返回值寄存器)中建立了最終結果。 –

+1

您的評論關於'vpbroadcastw'是完全錯誤的:大多數編譯器不會將'set1'編譯成常量的運行時廣播,因爲它很昂貴。'mov eax,0xff' /'movd xmm0,eax'/vpbroadcastw xmm0,xmm0' is Haswell上的3個uops,'vpbroadcastw xmm0,[mem16]'也是3個uops,動態生成比兩者都便宜(但編譯器傾向於把它們放在內存中),然而'vpbroadcastd'只有1個uop,即使沒有熔斷:它只需要一個加載端口,而不是ALU,所以你不需要浪費32B的內存在一個將被加載到循環外部的常量上 –