8

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

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

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

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

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

_mm_cmpgt_epu8(v1, v2)                                // #2

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

理想情况下,我正在寻找#1 的单指令解决方案和#2 的双指令解决方案。如果这些都不可能,那么我也对各种可能的 2 或 3 指令实现中的哪一个在现代 Intel CPU(Sandy Bridge、Ivy Bridge、Haswell)上最有效的想法感兴趣。


迄今为止案例 #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 吞吐量。

4

3 回答 3

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);
    }
于 2015-11-20T14:38:31.990 回答
2

我有一个想法>=在两个指令中做:

  • 无符号饱和减法
  • 与零比较

不过,这对 没有帮助>

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


此答案的先前版本有一个错误的想法,即b-a设置了符号位 iff a>b。但它实际上是需要测试的左侧一位:进位标志/位(对于打包整数 SIMD 不存在)。

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

于 2015-11-21T01:48:01.243 回答
2

本着从 SIMD 库复制代码的精神,Agner Fog 的矢量类库(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));
}
于 2015-11-20T21:57:33.863 回答