最优SSE无符号8位比较

时间:2015-11-20 10:26:22

标签: c x86 sse simd sse4

我试图找到使用SSE执行8位无符号比较的最多方法(直到SSE 4.2)。

我正在处理的最常见情况是比较> 0U,例如

_mm_cmpgt_epu8(v, _mm_setzero_si128())                // #1

(当然也可以认为是非零的简单测试。)

但我对更一般的情况也有些兴趣,例如

_mm_cmpgt_epu8(v1, v2)                                // #2

第一种情况可以用2条指令实现,使用各种不同的方法,例如与0比较然后反转结果。第二种情况通常需要3条指令,例如从两个操作数中减去128并执行带符号的比较。 (有关各种3种指令解决方案,请参阅this question。)

我理想的是#1的单指令解决方案和#2的双指令解决方案。如果这些都不可能,那么我也对在现代英特尔CPU(Sandy Bridge,Ivy Bridge,Haswell)上哪种各样可能的2或3指令实现最有效的想法感兴趣。

到目前为止,案例#2的最佳实施:

    1. 与无符号最大值进行比较并反转结果:
  

#define _mm_cmpgt_epu8(v0, v1) \ _mm_andnot_si128(_mm_cmpeq_epi8(_mm_max_epu8(v0, v1), v1), \ _mm_set1_epi8(-1))

两个算术指令+一个按位= 1.33吞吐量。

    1. 反转两个参数的符号位(==减去128)并使用带符号的比较:
  

#define _mm_cmpgt_epu8(v0, v1) \ _mm_cmpgt_epi8(_mm_xor_si128(v0, _mm_set1_epi8(-128)), \ _mm_xor_si128(v1, _mm_set1_epi8(-128)))

一个算术指令+两个按位= 1.16吞吐量。

案例#1的最佳实现,源自上面的案例#2实现:

  • 1
  

#define _mm_cmpgtz_epu8(v0) \ _mm_andnot_si128(_mm_cmpeq_epi8(v0, _mm_set1_epi8(0)), \ _mm_set1_epi8(-1))

一个算术指令+一个按位= 0.83吞吐量。

  • 2
  

#define _mm_cmpgtz_epu8(v0) \ _mm_cmpgt_epi8(_mm_xor_si128(v0, _mm_set1_epi8(-128)), \ _mm_set1_epi8(-128)))

一个算术指令+一个按位= 0.83吞吐量。

3 个答案:

答案 0 :(得分:4)

Simd Library有一个例子:

    const __m128i K_INV_ZERO = SIMD_MM_SET1_EPI8(0xFF);//_mm_set1_epi8(-1);

    SIMD_INLINE __m128i NotEqual8u(__m128i a, __m128i b)
    {
        return _mm_andnot_si128(_mm_cmpeq_epi8(a, b), K_INV_ZERO);
    }

    SIMD_INLINE __m128i Greater8u(__m128i a, __m128i b)
    {
        return _mm_andnot_si128(_mm_cmpeq_epi8(_mm_min_epu8(a, b), a), K_INV_ZERO);
    }

    SIMD_INLINE __m128i GreaterOrEqual8u(__m128i a, __m128i b)
    {
        return _mm_cmpeq_epi8(_mm_max_epu8(a, b), a);
    }

    SIMD_INLINE __m128i Lesser8u(__m128i a, __m128i b)
    {
        return _mm_andnot_si128(_mm_cmpeq_epi8(_mm_max_epu8(a, b), a), K_INV_ZERO);
    }

    SIMD_INLINE __m128i LesserOrEqual8u(__m128i a, __m128i b)
    {
        return _mm_cmpeq_epi8(_mm_min_epu8(a, b), a);
    }

答案 1 :(得分:2)

本着从SIMD库复制代码的精神,Agner Fog的Vector Class Library(C ++)是如何做到的:

// vector operator >= : returns true for elements for which a >= b (unsigned)
static inline Vec16cb operator >= (Vec16uc const & a, Vec16uc const & b) {
#ifdef __XOP__  // AMD XOP instruction set
    return _mm_comge_epu8(a,b);
#else  // SSE2 instruction set
    return _mm_cmpeq_epi8(_mm_max_epu8(a,b),a); // a == max(a,b)
#endif
}

// vector operator <= : returns true for elements for which a <= b (unsigned)
static inline Vec16cb operator <= (Vec16uc const & a, Vec16uc const & b) {
    return b >= a;
}

// vector operator > : returns true for elements for which a > b (unsigned)
static inline Vec16cb operator > (Vec16uc const & a, Vec16uc const & b) {
#ifdef __XOP__  // AMD XOP instruction set
    return _mm_comgt_epu8(a,b);
#else  // SSE2 instruction set
    return Vec16cb(Vec16c(~(b >= a)));
#endif
}

// vector operator < : returns true for elements for which a < b (unsigned)
static inline Vec16cb operator < (Vec16uc const & a, Vec16uc const & b) {
    return b > a;
}

// vector operator ~ : bitwise not
static inline Vec16uc operator ~ (Vec16uc const & a) {
    return Vec16uc( ~ Vec128b(a));
}

其中按位未定义为

// vector operator ~ : bitwise not
static inline Vec128b operator ~ (Vec128b const & a) {
    return _mm_xor_si128(a, _mm_set1_epi32(-1));
}

答案 2 :(得分:2)

我有一个想法,就是在两条指令中做>=

  • 使用无符号饱和度减去
  • 与零比较

>无效。

此外,它几乎等同于ErmIg的SIMD Library答案(max_epu8(a,b) - &gt; cmpeq with a),但更糟糕的是因为它需要一个归零寄存器。这适用于SSE2,而不是SSE4.1。 psubusb在与pminusb相同的端口上运行。

此答案的先前版本错误地认为b-a的符号位设为iff a>b。但它实际上是需要测试的左侧的一个位:进位标志/位(对于压缩整数SIMD不存在)。

请参阅编辑历史记录,了解有关将符号位广播到元素的其余部分的一些想法pshufb(否定结果)或pblendvb(可能是Skylake上的单个uop用于非仅限VEX版本。