使用 AVX/AVX2/SSE __m128i 将所有负数字节设置为 -128 (0x80) 并保留所有其他字节

Posted

技术标签:

【中文标题】使用 AVX/AVX2/SSE __m128i 将所有负数字节设置为 -128 (0x80) 并保留所有其他字节【英文标题】:With AVX/AVX2/SSE __m128i set all bytes that are negative to -128 (0x80) and leave all other bytes alone 【发布时间】:2020-06-26 04:13:57 【问题描述】:

基本上我要做的是获取一个__m128i 寄存器,并将每个负字节的值设置为-128 (0x80),而不更改任何正值。

确切是:

signed char __m128_as_char_arr[16] = some data;
for(int i = 0; i < 16; i++) 
     if (__m128_as_char_arr[i] < 0)  //alternative __m128_as_char_arr[i] & 0x80
           __m128_as_char_arr[i] = 0x80;
     


我认为最好的方法是:

__m128i v = some data;
int mask = _mm_movemask_epi8(_mm_cmpgt_epi8(_mm_set1_epi8(0xff), v));

// use mask in some way to only set chars with 1s bit set

但我不知道 (1) 使用什么指令仅设置与 mask 关联的字节,以及 (2) 是否有更好的方法来执行此操作(完全没有掩码或更好的方法生成掩码)。

【问题讨论】:

【参考方案1】:

您可以将这些值视为无符号并使用最小操作(_mm_min_epu8 et al),例如

v = _mm_min_epu8(v, _mm_set1_epi8(128));

这不仅是一种廉价的指令,还适用于 SSE2 及更高版本。

【讨论】:

【参考方案2】:

更新:@PaulR 想出了一个更好的主意。接受那个答案。 _mm_min_epu8 (1 uop) 至少与 _mm_blendv_epi8 (2 uop) 一样便宜,并且只需要 SSE2。


不如_mm_min_epu8 好,将其留在这里以防在min 技巧不完全有效的相关情况下有所帮助。

SSE4.1(以及 AVX 及更高版本)具有a variable-blend that selects based on the top bit of each byte。您可以将矢量用作混合控件和数据输入之一。

// SSE4.1 or AVX1.  Or for __m256i, AVX2
__m128i  negative_to_min(__m128i v)
    // take 2nd operand for elements of v where the high bit is set
    return _mm_blendv_epi8(v, _mm_set1_epi8(0x80), v);

仅使用 SSE2,您希望 0 &gt; vpcmpgtb 识别负面元素。直接的方法是在没有 pblendvb 的情况下使用通常的 AND/ANDN/OR 进行混合,但我们可以更加聪明,因为结果的最高位始终与输入的最高位匹配,并且我们想要的结果否定的情况实际上是x &amp; 0x80

                   // negative        non-neg
m = 0x80 ^ (0>x);  // 0x80             0x7f
x &= m;            // x&0x80 = 0x80    x & 0x7f = x
// SSE2
__m128i  negative_to_min(__m128i v)

    __m128i  neg = _mm_cmpgt_epi8(_mm_setzero_si128(), v);    // neg        non-neg
    __m128i  mask = _mm_xor_si128(neg, _mm_set1_epi8(0x80));  // 0x80   or  0x7f
    return   _mm_and_si128(mask, v);

这是更少的指令 (3),并且关键路径延迟不比 PCMPGTB / AND / ANDN / OR 差。它也不需要任何额外的movdqa 指令,如果它使用pxor xmm0,xmm0 廉价地生成一个零向量,然后将其覆盖为 pcmpgtb 目标。

如果您在其他地方使用0x7f 而不是0x80 常量,则可以与0x7f 异或并使用_mm_andn_si128(mask, v); 作为最后一步,以反转掩码。否则,最好使用可交换操作,让编译器更容易优化。


re:您的方法:如果没有 AVX512,movemask 就不是一个有用的构建块。没有 SIMD 方法可以将位图与矢量一起使用。在 AVX512 生成向量掩码而不是位掩码之前比较指令/内在函数,以便您可以将它们与 AND/ANDN/XOR/OR 位运算一起使用。

另外,您的 -1 &gt; v 会错误地将 -1 识别为非负数。

【讨论】:

以上是关于使用 AVX/AVX2/SSE __m128i 将所有负数字节设置为 -128 (0x80) 并保留所有其他字节的主要内容,如果未能解决你的问题,请参考以下文章

将 __m256i 设置为两个 __m128i 值的值

SSE:如何将 _m128i._i32[4] 减少到 _m128i._i8

如何在 MSVC 中高效地将两个 __m128d 转换为一个 __m128i?

AVX/SSE 将浮点符号掩码转换为 __m128i

将 16 位值的 __m256i 打包(饱和)到 8 位值的 __m128i?

如何将 16 字节的内存加载到 Rust __m128i 中?