C では、2 つの unsigned int 間の絶対差を計算するブランチレス手法はありますか? たとえば、変数 a と b が与えられた場合、a=3、b=5、または b=3、a=5 の場合は値 2 が必要です。理想的には、SSE レジスターを使用して計算をベクトル化できるようにしたいと考えています。
8 に答える
方法はいくつかありますが、一つだけ挙げておきます。
SSE4
PMINUDとを使用しPMAXUDて、レジスタ #1 の大きい値とレジスタ #2 の小さい値を区切ります。- それらを引きます。
MMX/SSE2
- 次の命令は符号付き整数の比較のみを受け入れるため、2 つの値の符号ビットを反転します。
PCMPGTD. この結果をマスクとして使用します。(a-b)との両方の結果を計算します。(b-a)POR ( PAND ( mask, a-b ), PANDN ( mask, b-a ) )絶対差の正しい値を選択するために使用します。
tommesani.comによると、この問題の解決策の 1 つは、飽和符号なし減算を 2 回使用することです。
飽和減算は 0 を下回らないため、次のように計算します。 r1 = (ab).saturating r2 = (ba).saturating
a が b より大きい場合、r1 には答えが含まれ、r2 は 0 になり、b>a の場合はその逆になります。2 つの部分的な結果を一緒に OR すると、目的の結果が得られます。
VTUNE のユーザー マニュアルによると、PSUBUSB/PSUBUSW は 128 ビット レジスタで使用できるため、この方法で大量の並列化を実現できるはずです。
max(i,j) - min(i,j)
(i>j)*(i-j) + (j>i)*(j-i)
確かにSSEレジスタを使用できますが、とにかくコンパイラがこれを行う場合があります
SSE2:
フェルノストの第2機能と同程度の速さのようです。GCC は、1 サイクル速くなるようにスケジュールすることもあれば、少し遅くなるようにスケジュールすることもあります。
__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:
以前よりもわずかに速くなりました。ループの外側の宣言方法によって、さまざまなバリエーションがあります。(たとえば、makeaとb volatilemake は物事をより速くします! スケジューリングに対するランダムな影響のようです。) しかし、これは一貫して 1 サイクルほど最速です。
__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 ) );
差を計算し、絶対値を返します
__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);
これにより、署名された比較操作を使用する場合よりも操作が1つ少なくなり、レジスターの圧力が少なくなります。
以前と同じ量のレジスタープレッシャー、2つの操作、依存関係チェーンのより良い分岐とマージ、uopsデコードのための命令ペアリング、および個別のユニット使用率。これには負荷が必要ですが、キャッシュが不足している可能性があります。この後、私はアイデアがありません。
__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万回の反復で各バージョンのタイミングを調整した後、違いは計り知れません。したがって、理解しやすいものを選択してください。
条件式はすべて非常に単純であるため、マシンとコンパイラによっては、以下の 1 つ以上がブランチレス コードになる可能性があります。
すべての sse 回答を確認したわけではありませんが、以下のいくつかはベクトル コードで表されている可能性があります。確かに、以下のすべてはベクトル化可能です (最初に unsigned 比較がある場合、または最初に 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
}
これを試してください(SSEを求めているという事実から判断すると、2番目の補数を想定しています):
int d = a-b;
int ad = ((d >> 30) | 1) * d;
説明: 符号ビット (ビット 31) が 1 番目のビットまで伝搬されます。| 1 つの部分は、乗数が 1 または -1 であることを保証します。乗算は最新の CPU で高速です。
えーと...とても簡単です...
int diff = abs( a - b );
簡単にベクトル化可能(SSE3を使用):
__m128i sseDiff = _mm_abs_epi32( _mm_sub_epi32( a, b ) );
aとbは符号なし整数です。a=0およびb=0xffffffffを考えます。正しい絶対差は0xffffffffですが、ソリューションでは1が得られます。