[英]The best way to shift a __m128i?
我需要將一個__m128i變量(比如說v)移位m位,這樣位就可以移動所有變量(所以,結果變量代表v * 2 ^ m)。 做這個的最好方式是什么?!
請注意_mm_slli_epi64單獨移動v0和v1:
r0 := v0 << count
r1 := v1 << count
所以v0的最后幾位錯過了,但我想將這些位移到r1。
編輯:我正在尋找一個比這更快的代碼(m <64):
r0 = v0 << m;
r1 = v0 >> (64-m);
r1 ^= v1 << m;
r2 = v1 >> (64-m);
對於編譯時恆定移位計數,您可以獲得相當好的結果。 否則不是真的。
這只是你問題中r0
/ r1
代碼的SSE實現,因為沒有其他明顯的方法可以做到這一點。 可變計數移位僅適用於向量元素內的位移,而不適用於整個寄存器的字節移位。 因此,我們只需將低64位傳輸到高64位,並使用可變計數移位將它們放在正確的位置。
// untested
#include <immintrin.h>
/* some compilers might choke on slli / srli with non-compile-time-constant args
* gcc generates the xmm, imm8 form with constants,
* and generates the xmm, xmm form with otherwise. (With movd to get the count in an xmm)
*/
// doesn't optimize for the special-case where count%8 = 0
// could maybe do that in gcc with if(__builtin_constant_p(count)) { if (!count%8) return ...; }
__m128i mm_bitshift_left(__m128i x, unsigned count)
{
__m128i carry = _mm_bslli_si128(x, 8); // old compilers only have the confusingly named _mm_slli_si128 synonym
if (count >= 64)
return _mm_slli_epi64(carry, count-64); // the non-carry part is all zero, so return early
// else
carry = _mm_srli_epi64(carry, 64-count); // After bslli shifted left by 64b
x = _mm_slli_epi64(x, count);
return _mm_or_si128(x, carry);
}
__m128i mm_bitshift_left_3(__m128i x) { // by a specific constant, to see inlined constant version
return mm_bitshift_left(x, 3);
}
// by a specific constant, to see inlined constant version
__m128i mm_bitshift_left_100(__m128i x) { return mm_bitshift_left(x, 100); }
我認為這不如原來那么方便。 _mm_slli_epi64
適用於gcc / clang / icc,即使計數不是編譯時常量(從整數reg生成一個movd
到xmm reg)。 有一個_mm_sll_epi64 (__m128i a, __m128i count)
(注意缺少i
),但至少這些天, i
內在可以生成任何形式的psllq
。
編譯時常量計數版本相當高效,可編譯為4條指令 (或5條沒有AVX):
mm_bitshift_left_3(long long __vector(2)):
vpslldq xmm1, xmm0, 8
vpsrlq xmm1, xmm1, 61
vpsllq xmm0, xmm0, 3
vpor xmm0, xmm0, xmm1
ret
這在Intel SnB / IvB / Haswell上具有3個周期延遲(vpslldq(1) - > vpsrlq(1) - > vpor(1)),吞吐量限制為每2個周期一個(使端口0上的向量移位單元飽和)。 字節移位在不同端口上的shuffle單元上運行。 立即計數向量移位都是單uop指令,因此當與其他代碼混合時,只有4個融合域uops占用管道空間。 (可變計數向量移位為2 uop,2個周期延遲,因此此函數的可變計數版本比計數指令更差。)
或者對於> = 64的計數:
mm_bitshift_left_100(long long __vector(2)):
vpslldq xmm0, xmm0, 8
vpsllq xmm0, xmm0, 36
ret
如果你的shift-count 不是編譯時常量,你必須在count> 64上進行分支,以確定是否向左移位或右移。 我相信移位計數被解釋為無符號整數,因此負數不可能。
它還需要額外的指令來將int
計數和64計數轉換為向量寄存器。 使用矢量比較和混合指令以無分支方式執行此操作可能是可能的,但分支可能是個好主意。
GP寄存器中__uint128_t
的可變計數版本看起來相當不錯; 比SSE版本更好。 Clang比gcc稍好一些,發出的mov
指令更少 ,但它仍然使用兩個cmov
指令來計算> = 64的情況。 (因為x86整數移位指令會掩蓋計數,而不是飽和。)
__uint128_t leftshift_int128(__uint128_t x, unsigned count) {
return x << count; // undefined if count >= 128
}
在SSE4.A中,指令insrq
和extrq
可用於一次移位(和旋轉)__mm128i 1-64位。 與8/16/32/64位對應的pextrN / pinsrX不同,這些指令在0到127之間的任何位偏移處選擇或插入m位(1到64之間)。需要注意的是,長度和偏移的總和不得超過128。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.