web-dev-qa-db-ja.com

どうすればブランチレスコードを作成できますか?

この回答に関連して: https://stackoverflow.com/a/11227902/471497

上記の回答では、分岐を回避することで分岐予測の失敗を回避する方法について説明しました。

ユーザーは、以下を置き換えることによってこれを示します。

if (data[c] >= 128)
{
    sum += data[c];
}

と:

int t = (data[c] - 128) >> 31;
sum += ~t & data[c];

これら2つはどのように同等ですか(特定のデータセットについては、厳密には同等ではありません)?

同様の状況で同様のことを行うことができる一般的な方法は何ですか?常に>>~を使用するのでしょうか?

22
Aequitas
_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)および_|_(または)も時々作用します。

27
Louis Wasserman

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

このバージョンが、ソートされたアレイとソートされていないアレイの両方で同等に高速に動作することを確認できます。

16
apangin

ブランチレスコードは、通常、セット[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
_
9
Aki Suihkonen