有人可以推荐一种使用英特尔内在函数(AVX、SSE4 ...)添加饱和 32 位有符号整数的快速方法吗?
我查看了内在函数指南,发现_mm256_adds_epi16
但这似乎只添加了 16 位整数。对于 32 位,我没有看到任何类似的东西。其他电话似乎环绕。
有人可以推荐一种使用英特尔内在函数(AVX、SSE4 ...)添加饱和 32 位有符号整数的快速方法吗?
我查看了内在函数指南,发现_mm256_adds_epi16
但这似乎只添加了 16 位整数。对于 32 位,我没有看到任何类似的东西。其他电话似乎环绕。
当(且仅当)以下情况时,将发生有符号溢出:
使用 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_min
andint_max
与 . 的符号位混合a
。此外,如果您只有 SSE2 或 SSE3,您可以通过算术移位(的overflow
)向右 31 位和手动混合(使用和/而不是/或)替换最后一个混合。
自然地,使用 AVX2,这可以使用__m256i
变量而不是__m128i
(应该很容易重写)。
附录如果您在编译时知道其中一个a
或一个的符号,您可以直接进行相应的设置,并且您可以保存两个计算,即为正数和负数(带)。b
saturated
_mm_xor_si128
overflow
_mm_andnot_si128(b, res)
a
_mm_andnot(res, b)
a
res = a+b
测试用例/演示:https ://godbolt.org/z/v1bsc85nG
这是适用于 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 我不确定如何改进它,所以我没有打扰。
这个链接回答了这个问题:
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++;
}
}