簡體   English   中英

轉移__m128i的最佳方法是什么?

[英]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中,指令insrqextrq可用於一次移位(和旋轉)__mm128i 1-64位。 與8/16/32/64位對應的pextrN / pinsrX不同,這些指令在0到127之間的任何位偏移處選擇或插入m位(1到64之間)。需要注意的是,長度和偏移的總和不得超過128。

暫無
暫無

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

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