使用 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
基本上我想做的是获取一个 __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) 是否有更好的方法来做到这一点(根本没有掩码或者更好的生成掩码的方法)。
更新:@PaulR 想出了一个更好的主意。接受那个答案。 _mm_min_epu8
(1 uop)至少和 _mm_blendv_epi8
(最多 2 uops)一样便宜,并且只需要 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
来识别负元素。直接的方法是通常的 AND/ANDN/OR 在没有 pblendvb 的情况下混合,但我们可以更聪明,因为结果的最高位总是与输入的最高位匹配,并且我们想要的结果否定的情况实际上是 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);
作为最后一步,以反转面具。否则,最好使用交换操作让编译器更容易优化。
回复:您的方法:如果没有 AVX512,movemask 就不是一个有用的构建块。没有 SIMD 方法可以将位图与矢量一起使用。在 AVX512 生成向量掩码而不是位掩码之前比较指令/内在函数,因此您可以将它们与 AND/ANDN/XOR/OR 按位运算一起使用。
此外,您的 -1 > v
会将 -1 误认为是非负数。
您可以将值视为无符号并使用最小运算(_mm_min_epu8
等),例如
v = _mm_min_epu8(v, _mm_set1_epi8(128));
除了是一条廉价的指令外,它还适用于 SSE2 及更高版本。
基本上我想做的是获取一个 __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) 是否有更好的方法来做到这一点(根本没有掩码或者更好的生成掩码的方法)。
更新:@PaulR 想出了一个更好的主意。接受那个答案。 _mm_min_epu8
(1 uop)至少和 _mm_blendv_epi8
(最多 2 uops)一样便宜,并且只需要 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
来识别负元素。直接的方法是通常的 AND/ANDN/OR 在没有 pblendvb 的情况下混合,但我们可以更聪明,因为结果的最高位总是与输入的最高位匹配,并且我们想要的结果否定的情况实际上是 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);
作为最后一步,以反转面具。否则,最好使用交换操作让编译器更容易优化。
回复:您的方法:如果没有 AVX512,movemask 就不是一个有用的构建块。没有 SIMD 方法可以将位图与矢量一起使用。在 AVX512 生成向量掩码而不是位掩码之前比较指令/内在函数,因此您可以将它们与 AND/ANDN/XOR/OR 按位运算一起使用。
此外,您的 -1 > v
会将 -1 误认为是非负数。
您可以将值视为无符号并使用最小运算(_mm_min_epu8
等),例如
v = _mm_min_epu8(v, _mm_set1_epi8(128));
除了是一条廉价的指令外,它还适用于 SSE2 及更高版本。