在C中是否存在无分支技术来计算两个无符号整数之间的绝对差值?例如,给定变量a和b,对于a = 3,b = 5或b = 3,a = 5的情况,我希望值为2。理想情况下,我还希望能够使用SSE寄存器对计算进行矢量化。
答案 0 :(得分:8)
有几种方法可以做到,我只提一个:
SSE4
PMINUD
和PMAXUD
分隔寄存器#1中较大的值和寄存器#2中较小的值。MMX / SSE2
PCMPGTD
。将此结果用作蒙版。(a-b)
和(b-a)
POR ( PAND ( mask, a-b ), PANDN ( mask, b-a ) )
为绝对差异选择正确的值。答案 1 :(得分:3)
max(i,j) - min(i,j)
(i>j)*(i-j) + (j>i)*(j-i)
你当然可以使用SSE寄存器,但编译器可能会为你做这件事
答案 2 :(得分:3)
从tommesani.com,这个问题的一个解决方案是使用饱和无符号减法两次。
由于饱和减法从不低于0,您计算: r1 =(a-b)。饱和 r2 =(b-a).saturating
如果a大于b,则r1将包含答案,r2将为0,反之亦然b> a。 将两个部分结果进行ORing将产生所需的结果。
根据the VTUNE users manual,PSUBUSB / PSUBUSW可用于128位寄存器,因此您应该可以通过这种方式获得大量并行化。
答案 3 :(得分:2)
计算差值并返回绝对值
__m128i diff = _mm_sub_epi32(a, b);
__m128i mask = _mm_xor_si128(diff, a);
mask = _mm_xor_si128(mask, b);
mask = _mm_srai_epi32(mask, 31);
diff = _mm_xor_si128(diff, mask);
mask = _mm_srli_epi32(mask, 31);
diff = _mm_add_epi32(diff, mask);
这需要少一个使用带符号比较操作的操作,并产生较少的寄存器压力。
与之前相同的寄存器压力,2个更多操作,更好的分支和合并依赖链,用于uop解码的指令配对和单独的单元利用率。虽然这需要加载,这可能超出缓存。在这之后我没有想法。
__m128i mask, diff;
diff = _mm_set1_epi32(-1<<31); // dependency branch after
a = _mm_add_epi32(a, diff); // arithmetic sign flip
b = _mm_xor_si128(b, diff); // bitwise sign flip parallel with 'add' unit
diff = _mm_xor_si128(a, b); // reduce uops, instruction already decoded
mask = _mm_cmpgt_epi32(b, a); // parallel with xor
mask = _mm_and_si128(mask, diff); // dependency merge, branch after
a = _mm_xor_si128(a, mask); // if 2 'bit' units in CPU, parallel with next
b = _mm_xor_si128(b, mask); // reduce uops, instruction already decoded
diff = _mm_sub_epi32(a, b); // result
在Core2Duo上对每个版本进行200万次迭代计时后,差异是无法估量的。所以选择更容易理解的东西。
答案 4 :(得分:2)
SSE2:
似乎与Phernost的第二个功能大致相同。有时GCC会将其调整为更快的完整周期,有时则稍慢一些。
__m128i big = _mm_set_epi32( INT_MIN, INT_MIN, INT_MIN, INT_MIN );
a = _mm_add_epi32( a, big ); // re-center the variables: send 0 to INT_MIN,
b = _mm_add_epi32( b, big ); // INT_MAX to -1, etc.
__m128i diff = _mm_sub_epi32( a, b ); // get signed difference
__m128i mask = _mm_cmpgt_epi32( b, a ); // mask: need to negate difference?
mask = _mm_andnot_si128( big, mask ); // mask = 0x7ffff... if negating
diff = _mm_xor_si128( diff, mask ); // 1's complement except MSB
diff = _mm_sub_epi32( diff, mask ); // add 1 and restore MSB
SSSE3:
比以前快一点。根据循环外部事物的声明方式,存在很多变化。 (例如,制作a
和b
volatile
会让事情变得更快!它似乎是对调度的随机影响。)但这一循环一直是最快的。
__m128i big = _mm_set_epi32( INT_MIN, INT_MIN, INT_MIN, INT_MIN );
a = _mm_add_epi32( a, big ); // re-center the variables: send 0 to INT_MIN,
b = _mm_add_epi32( b, big ); // INT_MAX to -1, etc.
__m128i diff = _mm_sub_epi32( b, a ); // get reverse signed difference
__m128i mask = _mm_cmpgt_epi32( b, a ); // mask: need to negate difference?
mask = _mm_xor_si128( mask, big ); // mask cannot be 0 for PSIGND insn
diff = _mm_sign_epi32( diff, mask ); // negate diff if needed
SSE4(thx rwong):
无法测试。
__m128i diff = _mm_sub_epi32( _mm_max_epu32( a, b ), _mm_min_epu32( a, b ) );
答案 5 :(得分:1)
尝试这个(假设第二个补充,可以通过你要求SSE来判断):
int d = a-b;
int ad = ((d >> 30) | 1) * d;
说明:符号位(第31位)传播到第1位。 | 1部分确保乘数为1或-1。现代CPU上的乘法很快。
答案 6 :(得分:0)
呃......很容易......
int diff = abs( a - b );
易于矢量化(使用SSE3):
__m128i sseDiff = _mm_abs_epi32( _mm_sub_epi32( a, b ) );
a和b是无符号整数。考虑a = 0和b = 0xffffffff。正确的绝对差值是0xffffffff,但您的解决方案将给出1。
答案 7 :(得分:0)
以下一个或多个可能导致无分支代码,具体取决于机器和编译器,因为条件表达式都非常简单。
我没有通过所有的sse答案,但可能下面的一些代码在向量代码中表示;当然以下所有都是可矢量化的(如果你有无符号比较开始,或者通过首先切换msb来伪造它。)。我认为对这个问题有一些实际的标量答案是有帮助的。
unsigned udiff( unsigned a, unsigned b )
{
unsigned result = a-b; // ok if a<b;
if(a <b ) result = -result;
return result;
}
unsigned udiff( unsigned a, unsigned b )
{
unsigned n =(a<b)? (unsigned)-1 : 0u;
unsigned result = a-b;
return (result^n)-n; // 'result' if n = 0; '-result' if n = 0xFFFFFFFF
}
unsigned udiff( unsigned a, unsigned b )
{
unsigned axb = a^b;
if( a < b ) axb = 0;
return (axb^b) - (axb^a); // a-b, or b-a
}
这适用于x86_64(或64位临时基本免费的任何东西)
unsigned udiff( unsigned a, unsigned b )
{
unsigned n= (unsigned)(
(long long)((unsigned long long)a - (unsigned long long)b)>>32
); // same n as 2nd example
unsigned result = a-b;
return (result^n)-n; // 'result' if n = 0; '-result' if n = 0xFFFFFFFF
}