带符号的 16 位 SSE 平均值
Posted
技术标签:
【中文标题】带符号的 16 位 SSE 平均值【英文标题】:signed 16-bit SSE average 【发布时间】:2012-08-28 04:07:18 【问题描述】:_mm_avg_epu16
通过PAVGW
提供两个无符号 16 位整数的平均值。正在转换为 float
并除以 2.
是使用 SSE 获得 两个有符号 16 位整数的平均值的唯一合适(最佳)方法,这是一个“有符号平均值,四舍五入,然后取反最高位”(@Mysticial),还是有其他方法?
编辑:这是我要优化的代码,到目前为止,我使用 SSE 的所有尝试都接近但不完全匹配,通常是围绕饱和/溢出包装的问题:
int16_t *a;
int16_t *b;
uint16_t *out;
out[i] = int((a[i] + b[i]) / 2.0f + 32768.5f)
尝试 #1:
const __m128i outputVal = _mm_add_epi16(_mm_avg_epu16(a, b), _mm_set1_epi16(32768));
尝试 #2:
const __m128i sum = _mm_add_epi16(a, b);
const __m128i outputVal = _mm_add_epi16(_mm_srai_epi16(sum, 1), _mm_set1_epi16(32768));
尝试 #3:
const __m128 elt_offset = _mm_set1_ps(32768.5f);
const __m128 avg_divisor = _mm_set1_ps(2.f);
const __m128i eltSum = _mm_add_epi16(edgeRowElts, edgeInnerRowElts); /* eltSum = int((inputData[i] + inputData[i + (direction*x)]) */
const __m64 eltSumLow = _mm_movepi64_pi64(eltSum); /* eltSumLow = (__m64) (0x0ffffffff & eltSum) */
const __m64 eltSumHigh = _mm_movepi64_pi64(_mm_srli_si128(eltSum, 8)); /* eltSumHigh = (__m64) (0x0ffffffff & (eltSum >> 64)) */
/* Lower */
__m128 eltSumF = _mm_cvtpi16_ps(eltSumLow); /* eltSumF = (float) eltSum; */
__m128 eltAvg = _mm_div_ps(eltSumF, avg_divisor); /* eltAvg = eltSum / 2.0f */
__m128 eltAvgOffset = _mm_add_ps(eltAvg, elt_offset); /* eltAvgOffset = eltAvg + 32768.5f */
const __m64 outputValLow = _mm_cvtps_pi16(eltAvgOffset); /* outputVal = (short) eltAvgOffset */
/* Upper */
eltSumF = _mm_cvtpi16_ps(eltSumHigh); /* eltSumF = (float) eltSum; */
eltAvg = _mm_div_ps(eltSumF, avg_divisor); /* eltAvg = eltSum / 2.0f */
eltAvgOffset = _mm_add_ps(eltAvg, elt_offset); /* eltAvgOffset = eltAvg + 32768.5f */
const __m64 outputValHigh = _mm_cvtps_pi16(eltAvgOffset); /* outputVal = (short) eltAvgOffset */
__m128i outputVal = _mm_slli_si128(_mm_movpi64_epi64(outputValHigh), 8); /* outputVal = (outputValHigh << 64); */
outputVal = _mm_or_si128(outputVal, _mm_movpi64_epi64(outputValLow)); /* outputVal = outputVal | (outputValLow); */
【问题讨论】:
嗯?你为什么要转换为浮动呢?将您的(数组)16 位整数放入内存中,将它们加载到__m128i
,调用_mm_avg_epu16
,然后解压缩__m128i
。
删除了我的答案,因为您似乎需要处理溢出以及适当的舍入。
看起来你想要一个有符号的平均值,然后向上取整,然后反转最高位。一种方法是对 32 位进行符号扩展来进行计算。然后转换回来。有一个用于扩展的 SSE4.1 指令,但不是向后的。所以你需要一些洗牌逻辑。
小事,32768
加和32768
异或是一样的。在当前的 Intel 处理器上,您可能更喜欢 xor 而不是 add,因为 xor 可以在所有执行单元中完成。 (而加法只能在其中几个中完成)
我不确定“四舍五入后反转最高位”是什么意思,但是您不能为此使用众所周知的位旋转 (x&y)+((x^y)>>1)
吗?那个计算平均值没有溢出,应该很容易转换为 SSE 指令。
【参考方案1】:
我不确定我是否完全理解这里的所有要求,但似乎:
a = _mm_add_epi16(a, _mm_set1_epi16(32768));
b = _mm_add_epi16(b, _mm_set1_epi16(32768));
outputVal = _mm_avg_epu16(a, b);
应该给你除了四舍五入要求之外的所有东西。
如果是这样,那么事后修正四舍五入应该不难:
round = _mm_xor_si128(a, b);
round = _mm_and_si128(round, _mm_set1_epi16(1));
outputVal = _mm_add_epi16(outputVal, round);
【讨论】:
_mm_avg_epu16
已经负责舍入。相当于(a + b + 1) / 2
OP 想要四舍五入,而不是四舍五入。
它已经四舍五入了。可能性如下:两个参数都是偶数或奇数 - 不进行四舍五入;其中一个参数是偶数,另一个是奇数 - 由于 + 1
部分,平均值 (XXX.5) 被四舍五入。
是的,这对于正面案例是正确的 - IIRC 虽然对于负面案例需要此修复,但我已经有一段时间没有看到这个了。以上是关于带符号的 16 位 SSE 平均值的主要内容,如果未能解决你的问题,请参考以下文章