如何最好地模拟 _mm_slli_si128(128 位位移)的逻辑含义,而不是 _mm_bslli_si128 bslli / bsrli 是相同 asm 指令的新的、更清晰的内部名称

翻阅 intel 内在指南,我看到了这个指令。查看命名模式,含义应该很清楚:“将 128 位寄存器左移固定位数”,但事实并非如此。实际上,它移动了固定数量的字节,这使得它与 _mm_bslli_si128 完全相同。

  • 这是疏忽吗?它不应该像 _mm_slli_epi32_mm_slli_epi64 这样的位移位吗?
  • 如果不是,我应该在哪种情况下在 _mm_bslli_si128 上使用它?
  • 是否有正确执行此操作的汇编指令?
  • 通过较小的班次来模拟这一点的最佳方法是什么?
Asp123Asp 回答:如何最好地模拟 _mm_slli_si128(128 位位移)的逻辑含义,而不是 _mm_bslli_si128 bslli / bsrli 是相同 asm 指令的新的、更清晰的内部名称

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

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

至于仿真,假设您有 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 年之前的某个时间,在 SSE2 _mm_slli_si128 被广泛使用之后很长时间)。我觉得它更清晰,并会推荐它用于新的开发。


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

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

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


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

所有 4 个主要 x86 编译器的当前版本 (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 AVX-512BW 版本仅使用 __m512i 名称。我认为,当 SSE2 和 AVX2 是新的时,_mm_slli_si128_mm256_slli_si256 是唯一可用的名称。当然,它晚于 SSE2 内在函数。

(请注意,si256si512 版本只是 16 字节操作的 2 或 4 个副本,不是跨 128 位通道移动字节;有些东西是很少有其他问答要求。这通常会使像这样的 AVX2 版本的 shuffle 和 palignr 比其他情况下有用得多:要么根本不值得使用,要么需要额外的 shuffle。)

我认为这个新的 bslli 名称是在 AVX-512 推出时引入的。英特尔在那个时候为其他内在函数发明了一些新名称,而 AVX-512 加载/存储内在函数采用 void* 而不是 __m512i*,这是对代码噪声量的重大改进,尤其是对于 C其中允许隐式转换为 void*。 (创建一个未对齐的 __m512i* is not actually a problem in C terms,但是你不能正常地取消它,所以这是一件看起来很奇怪的事情。)所以当时对内在命名进行了清理工作,我认为这是一部分。

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

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


注意 _mm256_loadu_si256( (const __m256i*)(arr + i) )256 之间的区别在某种程度上但并非完全有帮助:EPI = Extended (SSE 而不是 MMX) Packed Integer。 (打包意味着多个 SIMD 元素)。 epi16/32/64 表示一个完整的 128 位整数向量。

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

本文链接:https://www.f2er.com/966113.html

大家都在问