如何最好地模拟 _mm_slli_si128(128 位位移)的逻辑含义,而不是 _mm_bslli_si128

How to best emulate the logical meaning of _mm_slli_si128 (128-bit bit-shift), not _mm_bslli_si128

通过 intel intrinsics 指南,我看到了这条指令。看命名模式,意思应该很清楚:“将128位寄存器左移固定位数”,但事实并非如此。实际上它移动了固定数量的 字节 ,这使得它与 _mm_bslli_si128.

完全相同

1 这不是疏忽。该指令确实按字节移动,即 8 位的倍数。

2无所谓,_mm_slli_si128_mm_bslli_si128是等价的,都编译成pslldqSSE2指令。

至于仿真,我会那样做,假设你有 C++/17。如果您正在编写 C++/14,请将 if constexpr 替换为正常的 if,并在 static_assert.

中添加一条消息
template<int i>
inline __m128i shiftLeftBits( __m128i vec )
{
    static_assert( i >= 0 && i < 128 );
    // Handle couple trivial cases
    if constexpr( 0 == i )
        return vec;
    if constexpr( 0 == ( i % 8 ) )
        return _mm_slli_si128( vec, i / 8 );

    if constexpr( i > 64 )
    {
        // Shifting by more than 8 bytes, the lowest half will be all zeros
        vec = _mm_slli_si128( vec, 8 );
        return _mm_slli_epi64( vec, i - 64 );
    }
    else
    {
        // Shifting by less than 8 bytes.
        // Need to propagate a few bits across 64-bit lanes.
        __m128i low = _mm_slli_si128( vec, 8 );
        __m128i high = _mm_slli_epi64( vec, i );
        low = _mm_srli_epi64( low, 64 - i );
        return _mm_or_si128( low, high );
    }
}

TL:DR: 它们是同义词; bslli 名称较新,大约与新的 AVX-512 内在函数同时引入(2015 年之前的某个时间,long 在 SSE2 _mm_slli_si128 被广泛使用之后)。我发现它更清晰,并会推荐它用于新开发。


SSE/AVX2/AVX-512 没有元素大小大于 64 的移位。(或任何其他像 add 这样的位粒度操作,除了真正 128 完全独立的纯垂直按位布尔值操作,不是一个大的宽操作。或者为了 AVX-512 屏蔽和广播负载​​目的,可以在 dword 或 qword 块中,如 _mm512_xor_epi32 / vpxord)

您必须以某种方式模拟它,这对于编译时常数计数可能相当有效,因此您可以根据 c >= 64 在策略之间进行选择,c%8 的特殊情况减少到字节移位。现有的 SO 问答涵盖了这一点,或者查看@Soonts 对此问题的回答。

运行时变量计数很糟糕;您必须分支或两种方式都进行混合,这与 _mm_sll_epi64(v, _mm_cvtsi32_si128(i)) 可以编译为 movd / psllq xmm, xmm 的元素位移不同。不幸的是,byte-shuffle/shift 指令的硬件可变计数版本不存在,仅适用于位移版本。


bslli / bsrli 是相同 asm 指令的新的、更清晰的内部名称

x86 的所有 4 个主要编译器的当前版本 (Godbolt) 都支持 b 名称,我建议将它们用于新开发,除非您需要向后兼容旧的编译器,或者出于某种原因你 喜欢 旧名称,它既不能将它与不同的操作区分开来。 (例如,熟悉度;如果您不希望人们不得不在手册中查找这个新奇的名称。)

  • 自 4.8 以来的 gcc
  • clang 自 3.7
  • ICC 自 ICC13 或更早版本以来,Godbolt 没有更早的版本
  • 自 19.14 或更早版本以来的 MSVC,Godbolt 没有任何旧版本

如果您查看内在函数指南,_mm_slli_si128 被列为 PSLLDQ 的内在函数,这是一个字节移位。这不是一个错误,只是英特尔的一个笑话,或者他们在 SSE2 时代用来为内在函数选择名称的任何过程。 (计算机科学中只有两个难题:缓存失效和命名事物)。

Asm 助记符也使用相同的模式,即不使字节洗牌看起来与位移不同。 psllw xmm, 1 / pslld / psllq / pslldq。同样,您只需要知道 128 位大小是特殊的,并且必须是字节洗牌而不是位移位,因为 x86 从来没有。 (或者你必须查看手册。)

pslldq 的 asm 手册条目依次列出了其形式的内部函数,有趣的是只使用 b 名称作为 __m512i AVX-512BW 版本。我认为,当 SSE2 和 AVX2 是新的时,_mm_slli_si128_mm256_slli_si256 是唯一可用的名称。当然它 post-dates SSE2 内在函数。

(请注意,si256si512 版本只是 16 字节操作的 2 或 4 个副本, 而不是 在 128-位通道;一些其他问答要求的东西。这通常会使 AVX2 版本的洗牌像这样和 palignr 比它们本来的用处少得多:要么根本不值得使用,要么需要额外的洗牌最重要的。)

我认为这个新的 bslli 名称是在 AVX-512 是新的 时引入的。英特尔在那段时间为其他内在函数发明了一些新名称,AVX-512 load/store 内在函数使用 void* 而不是 __m512i*,这是对代码噪声量的重大改进,尤其是对于允许隐式转换为 void* 的 C。 (创建一个未对齐的 __m512i* ,但您不能正常取消引用它,所以这是一件看起来很奇怪的事情。)所以当时内部命名发生了清理工作,我认为这是一部分。

(AVX-512 也让 Intel 有机会引入一些相当糟糕的名字,比如 _mm_loadu_epi32(const void*) - 你会猜想这是一种严格的别名安全方式来执行 32 位 movd 加载,对吗?不,不幸的是,它是 vmovdqu32 xmm, [mem] 的内在函数,没有掩码。它只是 _mm_loadu_si128 指针 arg 具有不同的 C 类型。它的存在是为了与 [= 的命名模式保持一致46=]。void*__m128i__m256i 加载/存储内在函数会很好,但是如果它们有这样的误导性名称(尤其是当你不使用mask/maskz 附近代码中的版本),我将坚持使用那些繁琐的 _mm256_loadu_si256( (const __m256i*)(arr + i) ) 转换旧的内部函数,因为我喜欢输入三次 256。 >。 <

我希望 asm 更易于维护(或者内在函数只是使用 asm 助记符),因为它更简洁;英特尔通常在命名助记符方面做得很好。


注意 epi16/32/64si128 之间的区别在一定程度上但并非完全有帮助:EPI = 扩展(SSE 而不是 MMX)压缩整数。 (打包意味着多个 SIMD 元素)。 si128表示一个完整的128位整数向量。

无法从名称中推断出您不是只是对单个 128 位整数而不是压缩元素做同样的事情。您只需要知道,没有跨越 64 位边界的位粒度事物,只有 SIMD 随机播放(以字节为单位)。这避免了构建一个非常宽的桶形移位器的组合爆炸,或者为 128 位加法在如此长的距离进行进位传播,或其他任何东西。