この回答に関連して: https://stackoverflow.com/a/11227902/471497
上記の回答では、分岐を回避することで分岐予測の失敗を回避する方法について説明しました。
ユーザーは、以下を置き換えることによってこれを示します。
if (data[c] >= 128)
{
sum += data[c];
}
と:
int t = (data[c] - 128) >> 31;
sum += ~t & data[c];
これら2つはどのように同等ですか(特定のデータセットについては、厳密には同等ではありません)?
同様の状況で同様のことを行うことができる一般的な方法は何ですか?常に>>
と~
を使用するのでしょうか?
_int t = (data[c] - 128) >> 31;
_
ここでの秘訣は、_data[c] >= 128
_の場合、_data[c] - 128
_は非負であり、それ以外の場合は負であるということです。 int
の最上位ビットである符号ビットは、その数が負の場合に限り1です。 _>>
_は符号ビットを拡張するシフトであるため、右に31シフトすると、非負の場合はwhole結果が0になり、負でない場合はすべての1ビット(-1を表す)になります。以前は負でした。したがって、t
は_0
_の場合は_data[c] >= 128
_であり、それ以外の場合は_-1
_です。 _~t
_はこれらの可能性を切り替えるため、_~t
_は_-1
_の場合は_data[c] >= 128
_であり、それ以外の場合は_0
_です。
x & (-1)
は常にx
と等しく、_x & 0
_は常に_0
_と等しくなります。したがって、_sum += ~t & data[c]
_はsum
を_0
_の場合は_data[c] < 128
_、それ以外の場合は_data[c]
_だけ増加させます。
これらのトリックの多くは他の場所に適用できます。このトリックは確かに、ある値が別の値以上である場合にのみ数値を_0
_にし、それ以外の場合は_-1
_にするように一般的に適用できます。また、それをいじって取得することもできます。 _<=
_、_<
_など。このように少しいじることは、数学演算をブランチフリーにするための一般的なアプローチですが、常に同じ演算から構築されるとは限りません。 _^
_(xor)および_|
_(または)も時々作用します。
Louis Wassermanの答えは正しいですが、ブランチレスコードを書くためのより一般的な(そしてはるかに明確な)方法を紹介したいと思います。 ? :
演算子を使用できます。
int t = data[c];
sum += (t >= 128 ? t : 0);
JITコンパイラーは、実行プロファイルから、条件がここで十分に予測されていないことを確認します。このような場合、コンパイラは条件付きブランチを条件付き移動命令に置き換えるのに十分賢いです。
mov 0x10(%r14,%rbp,4),%r9d ; load R9d from array
cmp $0x80,%r9d ; compare with 128
cmovl %r8d,%r9d ; if less, move R8d (which is 0) to R9d
このバージョンが、ソートされたアレイとソートされていないアレイの両方で同等に高速に動作することを確認できます。
ブランチレスコードは、通常、セット[0、1]からweightを使用して条件ステートメントのすべての可能な結果を評価することを意味し、Sum {weight_i} = 1となります。ほとんどの計算は基本的に破棄されます。対応する重み_E_i
_(またはマスク_w_i
_)がゼロの場合、_m_i
_は正しい必要がないという事実からいくつかの最適化が生じる可能性があります。
_ result = (w_0 * E_0) + (w_1 * E_1) + ... + (w_n * E_n) ;; or
result = (m_0 & E_0) | (m_1 & E_1) | ... | (m_n * E_n)
_
ここで、m_iはビットマスクを表します。
水平方向の折りたたみを伴うE_iの並列処理によっても速度を達成できます。
これは、if (a) b; else c;
のセマンティクス、または[b、c]から1つの式のみが評価される3項省略形_a ? b : c
_と矛盾します。
したがって、三項演算はブランチレスコードの特効薬ではありません。まともなコンパイラは、ブランチレスコードを等しく生成します
_t = data[n];
if (t >= 128) sum+=t;
_
vs.
_ movl -4(%rdi,%rdx), %ecx
leal (%rax,%rcx), %esi
addl $-128, %ecx
cmovge %esi, %eax
_
ブランチレスコードのバリエーションには、ターゲットマシンに存在する場合、ABSなどの他のブランチレス非線形関数を介して問題を提示することが含まれます。
例えば.
_ 2 * min(a,b) = a + b - ABS(a - b),
2 * max(a,b) = a + b + ABS(a - b)
_
あるいは:
_ ABS(x) = sqrt(x*x) ;; caveat -- this is "probably" not efficient
_
_<<
_と_~
_に加えて、(おそらく未定義)の代わりにbool
と_!bool
_を使用することも同様に有益です(int >> 31)。同様に、条件が[0、1]と評価された場合、否定を使用して作業マスクを生成できます。
_-[0, 1] = [0, 0xffffffff] in 2's complement representation
_