使用 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 > v
和 pcmpgtb
识别负面元素。直接的方法是在没有 pblendvb 的情况下使用通常的 AND/ANDN/OR 进行混合,但我们可以更加聪明,因为结果的最高位始终与输入的最高位匹配,并且我们想要的结果否定的情况实际上是x & 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 > v
会错误地将 -1 识别为非负数。
【讨论】:
以上是关于使用 AVX/AVX2/SSE __m128i 将所有负数字节设置为 -128 (0x80) 并保留所有其他字节的主要内容,如果未能解决你的问题,请参考以下文章
SSE:如何将 _m128i._i32[4] 减少到 _m128i._i8
如何在 MSVC 中高效地将两个 __m128d 转换为一个 __m128i?