SSE中的乘法减法

时间:2015-06-19 16:51:30

标签: c vectorization sse

我正在渲染一段代码,在某些时候我有以下设置:

register m128 a = { 99,99,99,99,99,99,99,99 }
register m128 b = { 100,50,119,30,99,40,50,20 }

我目前正在这些寄存器中包装short,这就是我每个寄存器有8个值的原因。如果b的第i个值大于或等于中的值,我想要做的是a中的第i个元素和b中的相应值。 a(在这种情况下,a填充常量99)。为此,我首先使用大于或等于ba之间的操作,这样就产生了这个例子:

register m128 c = { 1,0,1,0,1,0,0,0 }

为了完成操作,我想使用乘法和减法,即存储b操作b -= a*c。结果将是:

b = { 1,50,20,30,0,40,50,20 }

有没有做这种事的操作?我发现的是Haswell的融合操作,但我目前正在研究Sandy-Bridge。此外,如果有人有更好的想法,请告诉我(例如,我可以做一个逻辑减法:如果c中的1,那么我减去,否则没有。

3 个答案:

答案 0 :(得分:3)

你基本上想要这个代码的SSE版本,对吗?

if (b >= a)
    t = b-a
else
    t = b
b = t

因为我们想要避免SSE版本的条件,所以我们可以像这样摆脱控制流(注意掩码是反转的):

uint16_t mask = (b>=a)-1
uint16_t tmp = b-a;
uint16_t d = (b & mask) | (tmp & ~mask)
b = d

我检查了_mm_cmpgt_epi16内在函数,它有一个很好的属性,它返回0x0000表示false或0xFFFF表示true,而不是单个位0或1(从而消除了对第一次减法的需要)。因此,我们的SSE版本可能如下所示。

__m128i mask = _mm_cmpgt_epi16 (b, a)
__m128i tmp = _mm_sub_epi16 (b, a)
__m128 d = _mm_or_ps (_mm_and_ps (mask, tmp), _mm_andnot_ps (mask, b))

编辑:哈罗德提到了一个不那么复杂的答案。如果您需要修改 if / else else 部分,上述解决方案可能会有所帮助。

uint16_t mask = ~( (b>=a)-1 )
uint16_t tmp = a & mask
b = b - tmp

SSE代码将是

__m128i mask = _mm_cmpgt_epi16 (b, a)
__m128i t = _mm_sub_epi16 (b, _mm_and_si128 (mask, a))

答案 1 :(得分:1)

另一种选择,如果您的输入是无符号的,您可以计算

b = min(b, b-a);

这是有效的,因为如果 a>bb-a 环绕并保证产生比 b 更大的值。对于 a<=b,您将始终获得介于 0b 之间的值。

b = _mm_min_epu16(b, _mm_sub_epi16(b,a));

所需的 _mm_min_epu16 需要 SSE4.1 或更高版本(_mm_min_epu8 只需要 SSE2)。

答案 2 :(得分:0)

您可以将b复制到c,从a减去c,在16位值中向右移动15个位置,补充{的值{1}},使用c屏蔽c,最后从a中减去c

我对内在函数语法并不熟悉,但步骤如下:

b

这是一个步骤更少的替代方案:

register m128 c = b;
c -= a;
c >>= 15;
c = ~c;
c &= a;
b -= c;