SSE2指令集-比较无符号整数

8

我对在添加无符号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”等效物可用,则在m3 [i] < m1 [i](溢出!)处,mask将具有0xFF,否则为0x00,我们将能够使用“or”夹紧m1,因此m1将保存有效的加法结果,并在溢出时保留0xFF
问题是,_mm_cmplt_epi8执行有符号比较,因此例如,如果m1 [i] = 0x70并且m2 [i] = 0x10,则m3 [i] = 0x80mask [i] = 0xFF,这显然不是我需要的。
使用VS2012。
我希望能有另一种方法来执行这个操作。谢谢!

1
当然,_mm_adds_epu8 已经饱和了结果。确定结果饱和的位置对于这里没有描述的计算是必要的。 - uv_
4个回答

13

使用 _mm_max_epu8 实现无符号 8 位向量的比较的一种方法是利用该函数返回的无符号 8 位整数元素的最大值。您可以将两个元素的(无符号)最大值与其中一个源元素进行相等比较,然后返回适当的结果。这对于 >=<= 来说需要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

7
其他答案让我想到了一个更简单的方法来直接回答这个问题:要简单地检测夹紧,可以进行饱和和非饱和加法,并比较结果。
__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掩码就已经准备好了。(如果没有AVX 3操作数非破坏性向量操作,则可能需要3个指令(额外的movdqa))。

如果非饱和加法结果为0xFF,则它将匹配饱和加法结果,并被检测为不剪裁。 这就是为什么它与仅检查饱和加法输出的0xFF字节不同。


1
这是一个更加“全面”的答案! - Paul R

2

另一种比较无符号字节的方式是:添加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对齐,类似于此答案所示。

1

这是一个比较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);
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接