3

有人可以推荐一种使用英特尔内在函数(AVX、SSE4 ...)添加饱和 32 位有符号整数的快速方法吗?

我查看了内在函数指南,发现_mm256_adds_epi16但这似乎只添加了 16 位整数。对于 32 位,我没有看到任何类似的东西。其他电话似乎环绕。

4

3 回答 3

3

当(且仅当)以下情况时,将发生有符号溢出:

  • 两个输入的符号相同,并且
  • 和的符号(加上环绕时)与输入不同

使用 C 运算符:overflow = ~(a^b) & (a^(a+b)).

此外,如果发生溢出,饱和结果将与任一输入具有相同的符号。使用int_min = int_max+1@PeterCordes 建议的技巧,并假设您至少有 SSE4.1 (for blendvps),这可以实现为:

__m128i __mm_adds_epi32( __m128i a, __m128i b )
{
    const __m128i int_max = _mm_set1_epi32( 0x7FFFFFFF );

    // normal result (possibly wraps around)
    __m128i res      = _mm_add_epi32( a, b );

    // If result saturates, it has the same sign as both a and b
    __m128i sign_bit = _mm_srli_epi32(a, 31); // shift sign to lowest bit
    __m128i saturated = _mm_add_epi32(int_max, sign_bit);

    // saturation happened if inputs do not have different signs, 
    // but sign of result is different:
    __m128i sign_xor  = _mm_xor_si128( a, b );
    __m128i overflow = _mm_andnot_si128(sign_xor, _mm_xor_si128(a,res));

    return _mm_castps_si128(_mm_blendv_ps( _mm_castsi128_ps( res ),
                                          _mm_castsi128_ps(saturated),
                                          _mm_castsi128_ps( overflow ) ) );
}

如果您blendvps的速度与移位和加法一样快(或更快)(还考虑端口使用情况),您当然可以将int_minandint_max与 . 的符号位混合a。此外,如果您只有 SSE2 或 SSE3,您可以通过算术移位(的overflow)向右 31 位和手动混合(使用和/而不是/或)替换最后一个混合。

自然地,使用 AVX2,这可以使用__m256i变量而不是__m128i(应该很容易重写)。

附录如果您在编译时知道其中一个a或一个的符号,您可以直接进行相应的设置,并且您可以保存两个计算,即为正数和负数(带)。bsaturated_mm_xor_si128overflow_mm_andnot_si128(b, res)a_mm_andnot(res, b)ares = a+b

测试用例/演示:https ://godbolt.org/z/v1bsc85nG

于 2019-06-11T13:17:30.357 回答
1

这是适用于 SSE2 的版本,对 SSE4.1 ( _mm_blendv_ps)、AVX-512VL ( _mm_ternarylogic_epi32) 和 AVX-512DQ ( _mm_movepi32_mask,根据 Peter Cordes 的建议) 进行了改进。

__m128i __mm_adds_epi32( __m128i a, __m128i b) {
  const __m128i int_max = _mm_set1_epi32(INT32_MAX);

  /* normal result (possibly wraps around) */
  const __m128i res = _mm_add_epi32(a, b);

  /* If result saturates, it has the same sign as both a and b */
  const __m128i sign_bit = _mm_srli_epi32(a, 31); /* shift sign to lowest bit */

  #if defined(__AVX512VL__)
    const __m128i overflow = _mm_ternarylogic_epi32(a, b, res, 0x42);
  #else
    const __m128i sign_xor = _mm_xor_si128(a, b);
    const __m128i overflow = _mm_andnot_si128(sign_xor, _mm_xor_si128(a, res));
  #endif

  #if defined(__AVX512DQ__) && defined(__AVX512VL__)
    return _mm_mask_add_epi32(res, _mm_movepi32_mask(overflow), int_max, sign_bit);
  #else
    const __m128i saturated = _mm_add_epi32(int_max, sign_bit);

    #if defined(__SSE4_1__)
      return
        _mm_castps_si128(
          _mm_blendv_ps(
            _mm_castsi128_ps(res),
            _mm_castsi128_ps(saturated),
            _mm_castsi128_ps(overflow)
          )
        );
    #else
      const __m128i overflow_mask = _mm_srai_epi32(overflow, 31);
      return
        _mm_or_si128(
          _mm_and_si128(overflow_mask, saturated),
          _mm_andnot_si128(overflow_mask, res)
        );
    #endif
  #endif
}

我这样做是为了SIMDe的 NEON vqaddq_s32(和 MSA __msa_adds_s_b)实现;如果您需要其他版本,您应该能够从simde/arm/neon/qadd.h调整它们。对于 128 位向量,除了 SSE 支持的(8/16 位,有符号和无符号)之外,还有:

  • vaddq_s32(想_mm_adds_epi32
  • vaddq_s64(想_mm_adds_epi64
  • vaddq_u32(想_mm_adds_epu32

vaddq_u64(think _mm_adds_epu64) 也存在,但目前依赖于向量扩展。我可以(并且可能应该)将生成的代码移植到内在函数,但是 TBH 我不确定如何改进它,所以我没有打扰。

于 2021-07-15T17:37:28.787 回答
0

这个链接回答了这个问题:

https://software.intel.com/en-us/forums/topic/285219

这是一个示例实现:

#include <immintrin.h>

__m128i __inline __mm_adds_epi32( __m128i a, __m128i b )
{
    static __m128i int_min = _mm_set1_epi32( 0x80000000 );
    static __m128i int_max = _mm_set1_epi32( 0x7FFFFFFF );

    __m128i res      = _mm_add_epi32( a, b );
    __m128i sign_and = _mm_and_si128( a, b );
    __m128i sign_or  = _mm_or_si128( a, b );

    __m128i min_sat_mask = _mm_andnot_si128( res, sign_and );
    __m128i max_sat_mask = _mm_andnot_si128( sign_or, res );

    __m128 res_temp = _mm_blendv_ps(_mm_castsi128_ps( res ),
                                    _mm_castsi128_ps( int_min ),
                                    _mm_castsi128_ps( min_sat_mask ) );

    return _mm_castps_si128(_mm_blendv_ps( res_temp,
                                          _mm_castsi128_ps( int_max ),
                                          _mm_castsi128_ps( max_sat_mask ) ) );
}

void addSaturate(int32_t* bufferA, int32_t* bufferB, size_t numSamples)
{
    //
    // Load and add
    //
    __m128i* pSrc1 = (__m128i*)bufferA;
    __m128i* pSrc2 = (__m128i*)bufferB;

    for(int i=0; i<numSamples/4; ++i)
    {
        __m128i res = __mm_adds_epi32(*pSrc1, *pSrc2);
        _mm_store_si128(pSrc1, res);

        pSrc1++;
        pSrc2++;
    }
}
于 2015-04-08T18:43:07.017 回答