SSE2 内在函数 - 比较无符号整数
SSE2 intrinsics - comparing unsigned integers
我对在添加无符号 8 位整数并将结果限制为 0xFF 时识别溢出值感兴趣:
__m128i m1 = _mm_loadu_si128(/* 16 8-bit unsigned integers */);
__m128i m2 = _mm_loadu_si128(/* 16 8-bit unsigned integers */);
__m128i m3 = _mm_adds_epu8(m1, m2);
我有兴趣对这些无符号整数执行“小于”比较,类似于 _mm_cmplt_epi8
有符号:
__m128i mask = _mm_cmplt_epi8 (m3, m1);
m1 = _mm_or_si128(m3, mask);
如果“epu8”等价物可用,mask
将有 0xFF
,其中 m3[i] < m1[i]
(溢出!),否则 0x00
,我们将能够使用“或”钳制 m1
,因此 m1
将在有效的地方保存加法结果,在溢出的地方保存 0xFF
。
问题是,_mm_cmplt_epi8
执行有符号比较,所以例如如果 m1[i] = 0x70
和 m2[i] = 0x10
,那么 m3[i] = 0x80
和 mask[i] = 0xFF
,这显然是不是我需要的。
使用 VS2012。
我希望能有另一种方法来执行此操作。谢谢!
实现无符号 8 位向量比较的一种方法是利用 _mm_max_epu8
,其中 return 是无符号 8 位 int 元素的最大值。您可以比较两个元素的(无符号)最大值与源元素之一的相等性,然后 return 适当的结果。这转化为 >=
或 <=
的 2 条指令,以及 >
或 <
.
的 3 条指令
示例代码:
#include <stdio.h>
#include <emmintrin.h> // SSE2
#define _mm_cmpge_epu8(a, b) \
_mm_cmpeq_epi8(_mm_max_epu8(a, b), a)
#define _mm_cmple_epu8(a, b) _mm_cmpge_epu8(b, a)
#define _mm_cmpgt_epu8(a, b) \
_mm_xor_si128(_mm_cmple_epu8(a, b), _mm_set1_epi8(-1))
#define _mm_cmplt_epu8(a, b) _mm_cmpgt_epu8(b, a)
int main(void)
{
__m128i va = _mm_setr_epi8(0, 0, 1, 1, 1, 127, 127, 127, 128, 128, 128, 254, 254, 254, 255, 255);
__m128i vb = _mm_setr_epi8(0, 255, 0, 1, 255, 0, 127, 255, 0, 128, 255, 0, 254, 255, 0, 255);
__m128i v_ge = _mm_cmpge_epu8(va, vb);
__m128i v_le = _mm_cmple_epu8(va, vb);
__m128i v_gt = _mm_cmpgt_epu8(va, vb);
__m128i v_lt = _mm_cmplt_epu8(va, vb);
printf("va = %4vhhu\n", va);
printf("vb = %4vhhu\n", vb);
printf("v_ge = %4vhhu\n", v_ge);
printf("v_le = %4vhhu\n", v_le);
printf("v_gt = %4vhhu\n", v_gt);
printf("v_lt = %4vhhu\n", v_lt);
return 0;
}
编译并运行:
$ gcc -Wall _mm_cmplt_epu8.c && ./a.out
va = 0 0 1 1 1 127 127 127 128 128 128 254 254 254 255 255
vb = 0 255 0 1 255 0 127 255 0 128 255 0 254 255 0 255
v_ge = 255 0 255 255 0 255 255 0 255 255 0 255 255 0 255 255
v_le = 255 255 0 255 255 0 255 255 0 255 255 0 255 255 0 255
v_gt = 0 0 255 0 0 255 0 0 255 0 0 255 0 0 255 0
v_lt = 0 255 0 0 255 0 0 255 0 0 255 0 0 255 0 0
其他答案让我想到了一种更简单的方法来更直接地回答特定问题:
为了简单地检测钳位,进行饱和和非饱和相加,并比较结果。
__m128i m1 = _mm_loadu_si128(/* 16 8-bit unsigned integers */);
__m128i m2 = _mm_loadu_si128(/* 16 8-bit unsigned integers */);
__m128i m1m2_sat = _mm_adds_epu8(m1, m2);
__m128i m1m2_wrap = _mm_add_epi8(m1, m2);
__m128i non_clipped = _mm_cmpeq_epi8(m1m2_sat, m1m2_wrap);
所以这只是 adds
之外的两条指令,其中一条可以 运行 与 adds
并行。所以 non_clipped
掩码在加法结果后一个周期准备就绪。 (可能有 3 条指令(一个额外的 movdqa),没有 AVX 3 操作数非破坏性向量操作。)
如果非饱和相加结果为0xFF,则匹配饱和相加结果,检测为不裁剪。这就是为什么它不同于仅仅检查 0xFF 字节的饱和加法输出。
另一种比较无符号字节的方法:添加 0x80
并将它们作为有符号字节进行比较。
__m128i _mm_cmplt_epu8(__m128i a, __m128i b) {
__m128i as = _mm_add_epi8(a, _mm_set1_epi8((char)0x80));
__m128i bs = _mm_add_epi8(b, _mm_set1_epi8((char)0x80));
return _mm_cmplt_epi8(as, bs);
}
我认为它不是很有效,但它确实有效,并且在某些情况下可能会有用。此外,如果需要,您可以使用 xor 而不是加法。
在某些情况下,您甚至可以同时进行双向范围检查,即将一个值与下限和上限进行比较。为此,将下限与 0x80
对齐,类似于 this answer 所做的。
有一个8位无符号整数比较的实现:
inline __m128i NotEqual8u(__m128i a, __m128i b)
{
return _mm_andnot_si128(_mm_cmpeq_epi8(a, b), _mm_set1_epi8(-1));
}
inline __m128i Greater8u(__m128i a, __m128i b)
{
return _mm_andnot_si128(_mm_cmpeq_epi8(_mm_min_epu8(a, b), a), _mm_set1_epi8(-1));
}
inline __m128i GreaterOrEqual8u(__m128i a, __m128i b)
{
return _mm_cmpeq_epi8(_mm_max_epu8(a, b), a);
}
inline __m128i Lesser8u(__m128i a, __m128i b)
{
return _mm_andnot_si128(_mm_cmpeq_epi8(_mm_max_epu8(a, b), a), _mm_set1_epi8(-1));
}
inline __m128i LesserOrEqual8u(__m128i a, __m128i b)
{
return _mm_cmpeq_epi8(_mm_min_epu8(a, b), a);
}
我对在添加无符号 8 位整数并将结果限制为 0xFF 时识别溢出值感兴趣:
__m128i m1 = _mm_loadu_si128(/* 16 8-bit unsigned integers */);
__m128i m2 = _mm_loadu_si128(/* 16 8-bit unsigned integers */);
__m128i m3 = _mm_adds_epu8(m1, m2);
我有兴趣对这些无符号整数执行“小于”比较,类似于 _mm_cmplt_epi8
有符号:
__m128i mask = _mm_cmplt_epi8 (m3, m1);
m1 = _mm_or_si128(m3, mask);
如果“epu8”等价物可用,mask
将有 0xFF
,其中 m3[i] < m1[i]
(溢出!),否则 0x00
,我们将能够使用“或”钳制 m1
,因此 m1
将在有效的地方保存加法结果,在溢出的地方保存 0xFF
。
问题是,_mm_cmplt_epi8
执行有符号比较,所以例如如果 m1[i] = 0x70
和 m2[i] = 0x10
,那么 m3[i] = 0x80
和 mask[i] = 0xFF
,这显然是不是我需要的。
使用 VS2012。
我希望能有另一种方法来执行此操作。谢谢!
实现无符号 8 位向量比较的一种方法是利用 _mm_max_epu8
,其中 return 是无符号 8 位 int 元素的最大值。您可以比较两个元素的(无符号)最大值与源元素之一的相等性,然后 return 适当的结果。这转化为 >=
或 <=
的 2 条指令,以及 >
或 <
.
示例代码:
#include <stdio.h>
#include <emmintrin.h> // SSE2
#define _mm_cmpge_epu8(a, b) \
_mm_cmpeq_epi8(_mm_max_epu8(a, b), a)
#define _mm_cmple_epu8(a, b) _mm_cmpge_epu8(b, a)
#define _mm_cmpgt_epu8(a, b) \
_mm_xor_si128(_mm_cmple_epu8(a, b), _mm_set1_epi8(-1))
#define _mm_cmplt_epu8(a, b) _mm_cmpgt_epu8(b, a)
int main(void)
{
__m128i va = _mm_setr_epi8(0, 0, 1, 1, 1, 127, 127, 127, 128, 128, 128, 254, 254, 254, 255, 255);
__m128i vb = _mm_setr_epi8(0, 255, 0, 1, 255, 0, 127, 255, 0, 128, 255, 0, 254, 255, 0, 255);
__m128i v_ge = _mm_cmpge_epu8(va, vb);
__m128i v_le = _mm_cmple_epu8(va, vb);
__m128i v_gt = _mm_cmpgt_epu8(va, vb);
__m128i v_lt = _mm_cmplt_epu8(va, vb);
printf("va = %4vhhu\n", va);
printf("vb = %4vhhu\n", vb);
printf("v_ge = %4vhhu\n", v_ge);
printf("v_le = %4vhhu\n", v_le);
printf("v_gt = %4vhhu\n", v_gt);
printf("v_lt = %4vhhu\n", v_lt);
return 0;
}
编译并运行:
$ gcc -Wall _mm_cmplt_epu8.c && ./a.out
va = 0 0 1 1 1 127 127 127 128 128 128 254 254 254 255 255
vb = 0 255 0 1 255 0 127 255 0 128 255 0 254 255 0 255
v_ge = 255 0 255 255 0 255 255 0 255 255 0 255 255 0 255 255
v_le = 255 255 0 255 255 0 255 255 0 255 255 0 255 255 0 255
v_gt = 0 0 255 0 0 255 0 0 255 0 0 255 0 0 255 0
v_lt = 0 255 0 0 255 0 0 255 0 0 255 0 0 255 0 0
其他答案让我想到了一种更简单的方法来更直接地回答特定问题:
为了简单地检测钳位,进行饱和和非饱和相加,并比较结果。
__m128i m1 = _mm_loadu_si128(/* 16 8-bit unsigned integers */);
__m128i m2 = _mm_loadu_si128(/* 16 8-bit unsigned integers */);
__m128i m1m2_sat = _mm_adds_epu8(m1, m2);
__m128i m1m2_wrap = _mm_add_epi8(m1, m2);
__m128i non_clipped = _mm_cmpeq_epi8(m1m2_sat, m1m2_wrap);
所以这只是 adds
之外的两条指令,其中一条可以 运行 与 adds
并行。所以 non_clipped
掩码在加法结果后一个周期准备就绪。 (可能有 3 条指令(一个额外的 movdqa),没有 AVX 3 操作数非破坏性向量操作。)
如果非饱和相加结果为0xFF,则匹配饱和相加结果,检测为不裁剪。这就是为什么它不同于仅仅检查 0xFF 字节的饱和加法输出。
另一种比较无符号字节的方法:添加 0x80
并将它们作为有符号字节进行比较。
__m128i _mm_cmplt_epu8(__m128i a, __m128i b) {
__m128i as = _mm_add_epi8(a, _mm_set1_epi8((char)0x80));
__m128i bs = _mm_add_epi8(b, _mm_set1_epi8((char)0x80));
return _mm_cmplt_epi8(as, bs);
}
我认为它不是很有效,但它确实有效,并且在某些情况下可能会有用。此外,如果需要,您可以使用 xor 而不是加法。
在某些情况下,您甚至可以同时进行双向范围检查,即将一个值与下限和上限进行比较。为此,将下限与 0x80
对齐,类似于 this answer 所做的。
有一个8位无符号整数比较的实现:
inline __m128i NotEqual8u(__m128i a, __m128i b)
{
return _mm_andnot_si128(_mm_cmpeq_epi8(a, b), _mm_set1_epi8(-1));
}
inline __m128i Greater8u(__m128i a, __m128i b)
{
return _mm_andnot_si128(_mm_cmpeq_epi8(_mm_min_epu8(a, b), a), _mm_set1_epi8(-1));
}
inline __m128i GreaterOrEqual8u(__m128i a, __m128i b)
{
return _mm_cmpeq_epi8(_mm_max_epu8(a, b), a);
}
inline __m128i Lesser8u(__m128i a, __m128i b)
{
return _mm_andnot_si128(_mm_cmpeq_epi8(_mm_max_epu8(a, b), a), _mm_set1_epi8(-1));
}
inline __m128i LesserOrEqual8u(__m128i a, __m128i b)
{
return _mm_cmpeq_epi8(_mm_min_epu8(a, b), a);
}