【问题标题】:SSE2 intrinsics - comparing unsigned integersSSE2 内在函数 - 比较无符号整数
【发布时间】:2016-01-01 22:06:39
【问题描述】:

我有兴趣在添加无符号 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] = 0x70m2[i] = 0x10,那么m3[i] = 0x80mask[i] = 0xFF,这显然不是我需要的。

使用 VS2012。

我希望有另一种方法来执行此操作。谢谢!

【问题讨论】:

  • 当然,_mm_adds_epu8 已经饱和了结果。确定此处未描述的计算需要饱和结果的位置。

标签: c++ x86 sse simd intrinsics


【解决方案1】:

实现无符号 8 位向量比较的一种方法是利用 _mm_max_epu8,它返回最大的无符号 8 位 int 元素。您可以将两个元素的(无符号)最大值与源元素之一进行比较,然后返回适当的结果。这将转换为 >=<= 的 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

【讨论】:

    【解决方案2】:

    其他答案让我想到了一种更简单的方法来更直接地回答具体问题:

    为了简单地检测钳位,进行饱和和非饱和加法,并比较结果。

    __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 字节。

    【讨论】:

    • 这是一个更“全面”的答案!
    【解决方案3】:

    比较无符号字节的另一种方法:添加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 所做的。

    【讨论】:

      【解决方案4】:

      有一个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);
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-10-19
        • 1970-01-01
        • 1970-01-01
        • 2023-03-12
        • 2021-03-28
        • 2014-06-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多