使用SSE计算无符号整数之间的绝对差值

时间:2010-08-01 04:52:52

标签: c++ unsigned sse

在C中是否存在无分支技术来计算两个无符号整数之间的绝对差值?例如,给定变量a和b,对于a = 3,b = 5或b = 3,a = 5的情况,我希望值为2。理想情况下,我还希望能够使用SSE寄存器对计算进行矢量化。

8 个答案:

答案 0 :(得分:8)

有几种方法可以做到,我只提一个:

SSE4

  • 使用PMINUDPMAXUD分隔寄存器#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:

比以前快一点。根据循环外部事物的声明方式,存在很多变化。 (例如,制作ab 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
}