GCCが分岐予測を常に特定の方法で実行するように強制するコンパイラヒントはありますか?


118

Intelアーキテクチャの場合、GCCコンパイラに、コード内の特定の方法で常に分岐予測を強制するコードを生成するように指示する方法はありますか?Intelハードウェアはこれをサポートしていますか?他のコンパイラやハードウェアはどうですか?

私はこれをC ++コードで使用します。高速で実行したい場合がわかっており、他の分岐が最近必要になった場合でも、他の分岐を取得する必要があるときにスローダウンを気にしません。

for (;;) {
  if (normal) { // How to tell compiler to always branch predict true value?
    doSomethingNormal();
  } else {
    exceptionalCase();
  }
}

Evdzhan Mustafaの質問に続くように、ヒントは、プロセッサが命令に初めて遭遇したときにヒントを指定するだけでよく、その後のすべての分岐予測は正常に機能しますか?


何かが異常になった場合にも例外をスローする可能性があります(コンパイラに依存しません)
Shep

2
密接に関連している:Linuxカーネルの
マイケルハンプトン

回答:


9

C ++ 20以降、可能性の高い属性と可能性の低い属性が標準化され、g ++ 9ですでにサポートさています。ここで説明したように、あなたは書くことができます

if (a>b) {
  /* code you expect to run often */
  [[likely]] /* last statement */
}

たとえば、次のコードでは、ifブロックのおかげでelseブロックがインライン化さ[[unlikely]]れます

int oftendone( int a, int b );
int rarelydone( int a, int b );
int finaltrafo( int );

int divides( int number, int prime ) {
  int almostreturnvalue;
  if ( ( number % prime ) == 0 ) {
    auto k                         = rarelydone( number, prime );
    auto l                         = rarelydone( number, k );
    [[unlikely]] almostreturnvalue = rarelydone( k, l );
  } else {
    auto a            = oftendone( number, prime );
    almostreturnvalue = oftendone( a, a );
  }
  return finaltrafo( almostreturnvalue );
}

属性の存在/不在を比較するゴッドボルトリンク


なぜ使用[[unlikely]]してif[[likely]]else
WilliamKF

理由はありません。属性がどこに行く必要があるかを試してみたところ、この星座になってしまいました。
セイファート

かなりクール。残念ながら、この方法は古いC ++バージョンには適用できません。
Maxim Egorushkin

ファンタスティックゴッドボルトリンク
ルイスケルシー

87

GCCは、__builtin_expect(long exp, long c)このような機能を提供する機能をサポートしています。ここでドキュメントを確認できます

どこexp条件が使用され、c期待値です。たとえばあなたの場合あなたが望むだろう

if (__builtin_expect(normal, 1))

厄介な構文のため、これは通常、次のような2つのカスタムマクロを定義することによって使用されます。

#define likely(x)    __builtin_expect (!!(x), 1)
#define unlikely(x)  __builtin_expect (!!(x), 0)

タスクを簡単にするためだけです。

次のことに注意してください。

  1. これは非標準です
  2. コンパイラ/ cpu分岐予測子は、そのようなことを決定するのにあなたよりも熟練している可能性が高いため、これは時期尚早なマイクロ最適化になる可能性があります

3
constexpr関数ではなくマクロを表示する理由はありますか?
コロンボ2015年

22
@Columbo:constexpr関数このマクロ置き換えることができるとは思いません。それはif私が直接信じている声明になければなりません。同じ理由assertconstexpr関数になることはありません。
Mooing Duck

1
@MooingDuck同意しますが、断定する理由にもあります。
Shafik Yaghmour

7
@Columboマクロを使用する1つの理由は、これがCまたはC ++で関数よりも意味的に正しいマクロが数少ない場所の1つであるためです。関数は最適化のためにのみ機能するように見えます(これ最適化constexprです。実装固有のアセンブリのインライン化ではなく、値のセマンティクスについてのみ話します)。コードの単純な解釈(インラインなし)は無意味です。このために関数を使用する理由はまったくありません。
Leushenko、

2
@Leushenkoそれ__builtin_expect自体が最適化のヒントであると考えると、その使用を単純化する方法は最適化に依存すると主張するのは...説得力がない。また、constexpr指定子を追加して最初から機能させるのではなく、定数式で機能させるようにしました。そして、はい、関数を使用する理由があります。たとえば、名前空間全体をのようなかわいい名前で汚染したくありませんlikelyLIKELYマクロであることを強調し、衝突を避けるために、たとえばを使用する必要がありますが、それは単に醜いです。
コロンボ2015年

46

gccには長い__builtin_expect(long exp、long c)があります鉱山を強調):

__builtin_expectを使用して、コンパイラに分岐予測情報を提供できます。プログラマーはプログラムが実際にどのように機能するかを予測することで悪名高いため、一般に、これには実際のプロファイルフィードバックを使用することをお勧めします(-fprofile-arcs)。ただし、このデータを収集するのが難しいアプリケーションがあります。

戻り値はexpの値であり、整数式でなければなりません。組み込みのセマンティクスは、exp == cであることが期待されていることです。例えば:

if (__builtin_expect (x, 0))
   foo ();

xがゼロであることを期待しているので、fooを呼び出すことを期待していないことを示します。expの積分式に制限されているため、次のような構文を使用する必要があります

if (__builtin_expect (ptr != NULL, 1))
   foo (*ptr);

ポインタまたは浮動小数点値をテストするとき。

ドキュメントの注記として、実際のプロファイルフィードバックを使用することをお勧めします。この記事では、これの実際的な例と、少なくともその使用例がを使用した場合の改善になる方法を示し__builtin_expectます。g ++でプロファイルに基づく最適化を使用する方法もご覧ください

我々はまた、見つけることができます)(おそらくカーネルのマクロ(上のLinuxカーネル初心者の記事を)し、そう、この機能を使用しています:

#define likely(x)       __builtin_expect(!!(x), 1)
#define unlikely(x)     __builtin_expect(!!(x), 0)

!!マクロで使用されていることに注意してください。これについての説明は、(条件)ではなく!!(条件)を使用する理由にあります。

この手法がLinuxカーネルで使用されているからといって、常にそれを使用する意味があるとは限りません。この質問から、最近パラメーターをコンパイル時定数または変数として渡すときの関数のパフォーマンスの違いに答えたことがわかります。多くの手動の最適化手法は一般的なケースでは機能しません。テクニックが有効かどうかを理解するには、コードを慎重にプロファイリングする必要があります。古いテクニックの多くは、最新のコンパイラ最適化とは関係がない場合もあります。

組み込み関数は移植可能ではありませんが、clangは__builtin_expectもサポートしています

また、一部のアーキテクチャでは、違いが生じない場合があります。


Linuxカーネルにとって十分なものは、C ++ 11には不十分です。
Maxim Egorushkin 2017年

@MaximEgorushkinのメモ、私は実際にはその使用を推奨していません。実際、私の最初の引用である引用したgccのドキュメントでは、その手法も使用していません。私の答えの主な目的は、このルートに進む前に代替案を慎重に検討することだと思います。
Shafik Yaghmour

44

いいえ、ありません。(少なくとも最近のx86プロセッサでは。)

__builtin_expect他の回答で言及されているのは、gccがアセンブリコードを配置する方法に影響します。CPUの分岐予測子に直接影響しません。もちろん、コードの並べ替えが原因で分岐予測に間接的な影響があります。しかし、最近のx86プロセッサでは、「この分岐が実行される/実行されないと想定」するようにCPUに指示する命令はありません。

詳細については、この質問を参照してください:実際に使用されるIntel x86 0x2E / 0x3E Prefix Branch Prediction?

明確にするため、__builtin_expectおよび/またはの使用が-fprofile-arcs でき、両方のコードレイアウトによって分岐予測器にヒントを与えることによって、あなたのコードのパフォーマンスを向上させる(参照-アライメントと分岐予測のx86-64アセンブリのパフォーマンス最適化を、また、キャッシュの動作を向上させること) 「可能性が低い」コードを「可能性が高い」コードから遠ざけることによって。


9
これは誤りです。x86のすべての最新バージョンでは、デフォルトの予測アルゴリズムは、前方分岐が行われず、後方分岐が行われることを予測します(software.intel.com/en-us/articles/…を参照)。したがって、コードを再配置することで、CPUに効果的なヒントを与えることができます。これは、を使用したときにGCCが行うこととまったく同じです__builtin_expect
Nemo

6
@ニーモ、私の答えの最初の文を読み終わった?あなたが言ったすべては私の答えまたは与えられたリンクでカバーされています。「分岐予測を常に特定の方向に進めるように強制する」ことができるかという質問の答えは「いいえ」であり、これについて他の答えが十分に明確であるとは思いませんでした。
Artelius

4
よし、もっと注意深く読むべきだった。この答えは技術的には正しいようですが、質問者が明らかに探しているため、役に立たないようです__builtin_expect。したがって、これは単なるコメントである必要があります。しかし、それは間違いではないので、私は自分の反対票を削除しました。
Nemo

IMO役に立たないわけではありません。これは、CPUとコンパイラが実際にどのように機能するかを説明するのに役立ちます。これは、これらのオプションを使用した場合と使用しない場合のパフォーマンス分析に関連する場合があります。たとえば、通常は使用__builtin_expectして、簡単にテストケースを作成することはできません。テストケースを使用するとperf stat、非常に高い分岐予測ミス率を測定できます。ブランチのレイアウトに影響するだけです。ところで、IntelはSandybridgeまたは少なくともHaswellから静的予測をほとんど/ まったく使用していませ。古いエイリアスであるかどうかにかかわらず、BHTには常に何らかの予測があります。 xania.org/201602/bpu-part-two
Peter Cordes

24

C ++ 11でマクロを定義する正しい方法は次のとおりです。

#define LIKELY(condition) __builtin_expect(static_cast<bool>(condition), 1)
#define UNLIKELY(condition) __builtin_expect(static_cast<bool>(condition), 0)

このメソッドは、とは異なり[[likely]]、すべてのC ++バージョンと互換性がありますが、非標準の拡張に依存してい__builtin_expectます。


これらのマクロがこのように定義された場合:

#define LIKELY(condition) __builtin_expect(!!(condition), 1)

これにより、ifステートメントの意味が変わり、コードが壊れる可能性があります。次のコードを検討してください。

#include <iostream>

struct A
{
    explicit operator bool() const { return true; }
    operator int() const { return 0; }
};

#define LIKELY(condition) __builtin_expect((condition), 1)

int main() {
    A a;
    if(a)
        std::cout << "if(a) is true\n";
    if(LIKELY(a))
        std::cout << "if(LIKELY(a)) is true\n";
    else
        std::cout << "if(LIKELY(a)) is false\n";
}

そしてその出力:

if(a) is true
if(LIKELY(a)) is false

ご覧のとおり、LIKELYを!!キャストとして使用すると、boolのセマンティクスが壊れifます。

ここでのポイントは、ということではありませんoperator int()し、operator bool()関連する必要があります。これは良い習慣です。

代わりにを使用!!(x)するとstatic_cast<bool>(x)C ++ 11コンテキスト変換のコンテキストが失われます。


コンテキスト変換は2012年の欠陥を介して行われ、2014年後半でも実装の相違があったことに注意してください。実際、私がリンクしたケースはまだgccでは機能しないようです。
Shafik Yaghmour 2017年

@ShafikYaghmour switchおかげで、に含まれるコンテキスト変換に関して興味深い観察になります。ここに含まれるコンテキスト変換はタイプにbool固有でありそこリストされている5つの特定のコンテキストにswitchコンテキストは含まれません。
Maxim Egorushkin 2017年

これはC ++にのみ影響しますよね?したがって(_Bool)(condition)、Cには演算子のオーバーロードがないため、既存のCプロジェクトを使用するように変更する理由はありません。
Peter Cordes

2
あなたの例では(condition)、ではなく、単に使用しました!!(condition)。どちらもtrueそれを変更した後です(g ++ 7.1でテスト済み)。!!ブール化に使用するときに話している問題を実際に示す例を作成できますか?
Peter Cordes 2017

3
Peter Cordesが指摘したように、「これらのマクロがこのように定義されている場合:」と言い、次に '!!'を使用してマクロを表示すると、「ifステートメントの意味が変わってコードが壊れる可能性があります。次のコードを検討してください。」 ...そして、「!!」を使用しないコードを表示します まったく-C ++ 11より前でも壊れていることがわかっています。答えを変更して、指定されたマクロ(!!を使用)が間違っている例を示してください。
カルロウッド

18

他の答えはすべて適切に示唆されているので、を使用__builtin_expectして、コンパイラにアセンブリコードの配置方法に関するヒントを与えることができます。公式ドキュメントを指摘し、ほとんどの場合、アセンブラはGCCチームによって作られたものとしては良いようではありませんあなたの脳に組み込まれています。推測するよりも、実際のプロファイルデータを使用してコードを最適化することが常に最善です。

同様の行に沿っていますが、まだ言及されていませんが、コンパイラーに「コールド」パスでコードを生成させるGCC固有の方法です。これには、属性noinlinecold属性の使用が含まれます。これらの属性は関数にのみ適用できますが、C ++ 11ではインラインのラムダ関数を宣言でき、これら2つの属性はラムダ関数にも適用できます。

これはまだマイクロ最適化の一般的なカテゴリに分類されるため、標準的なアドバイスが適用されますが(テストは推測ではありません)、それよりも一般的に役立つと思い__builtin_expectます。x86プロセッサーのどの世代でも分岐予測ヒント(参照)を使用することはほとんどないため、とにかく影響を与えることができるのはアセンブリコードの順序だけです。エラー処理または「エッジケース」コードとは何かがわかっているので、このアノテーションを使用して、コンパイラーが分岐を予測せず、サイズを最適化するときに「ホット」コードからリンクしないようにすることができます。

使用例:

void FooTheBar(void* pFoo)
{
    if (pFoo == nullptr)
    {
        // Oh no! A null pointer is an error, but maybe this is a public-facing
        // function, so we have to be prepared for anything. Yet, we don't want
        // the error-handling code to fill up the instruction cache, so we will
        // force it out-of-line and onto a "cold" path.
        [&]() __attribute__((noinline,cold)) {
            HandleError(...);
        }();
    }

    // Do normal stuff
    
}

さらに良いことに、GCCは自動的にこれを無視して、利用可能な場合(たとえばでコンパイルする場合-fprofile-use)にプロファイルフィードバックを優先します。

こちらの公式ドキュメントをご覧くださいhttps : //gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes


2
分岐予測ヒント接頭辞は必要ないため無視されます。コードを並べ替えるだけで、まったく同じ効果を得ることができます。(デフォルトの分岐予測アルゴリズムでは、後方分岐が行われ、前方分岐は行われないと推測されます。)そのため、実際にはCPUにヒントを与えることができます__builtin_expect。全然無駄ではありません。あなたは正しいcold属性にも便利ですが、あなたのユーティリティを過小評価__builtin_expectだと思います。
Nemo

最近のIntel CPUは静的分岐予測を使用していません。あなたが説明するアルゴリズム、@ Nemo、後方分岐が行われると予測され、前方分岐が行われないと予測される場合、以前のプロセッサで使用され、Pentium Mまで使用されていましたが、現代の設計は基本的にランダムに推測し、その分岐にインデックスを付けますそのブランチに関する情報を見つけ、そこにある情報を使用することが期待される場所のテーブル(それは本質的にゴミである可能性があります)。したがって、分岐予測のヒントは理論的には便利ですが、実際には役に立たない可能性があるため、インテルはそれらを削除しました。
コーディグレイ

明確にするために、分岐予測の実装は非常に複雑で、コメントのスペースの制約により、大幅に単純化しすぎました。これは実際にはそれ自体で完全な答えになります。Haswellのような現代のマイクロアーキテクチャーには、まだ静的分岐予測の痕跡があるかもしれませんが、以前ほど単純ではありません。
コーディグレイ

「最新のIntel CPUは静的分岐予測を使用しない」のリファレンスはありますか?Intel自身の記事(software.intel.com/en-us/articles/…)は別の言い方をしています...しかし、それは2011年からです
Nemo

@Nemoの公式リファレンスはありません。インテルは、自社のチップで使用される分岐予測アルゴリズムに非常に精通しており、それらを企業秘密として扱います。知られていることのほとんどは、経験的なテストによって把握されています。相変わらず、Agner Fogの資料は最高のリソースですが、彼はさらに次のように述べています。残念ながら、静的BPがもう使用されていないことを示すベンチマークを最初に見た場所を思い出せません。
コーディグレイ

5

__builtin_expectを使用して、分岐が進むと予想される方法をコンパイラーに指示できます。これは、コードの生成方法に影響を与える可能性があります。一般的なプロセッサは、コードを順次より速く実行します。だからあなたが書くなら

if (__builtin_expect (x == 0, 0)) ++count;
if (__builtin_expect (y == 0, 0)) ++count;
if (__builtin_expect (z == 0, 0)) ++count;

コンパイラは次のようなコードを生成します

if (x == 0) goto if1;
back1: if (y == 0) goto if2;
back2: if (z == 0) goto if3;
back3: ;
...
if1: ++count; goto back1;
if2: ++count; goto back2;
if3: ++count; goto back3;

ヒントが正しければ、実際にブランチを実行せずにコードを実行します。通常のシーケンスよりも高速に実行されます。各ifステートメントは条件付きコードの周りを分岐し、3つの分岐を実行します。

新しいx86プロセッサーには、分岐が予想される分岐または分岐が予想されない分岐に対する命令があります(命令の接頭辞があり、詳細については不明です)。プロセッサがそれを使用しているかどうかはわかりません。分岐予測はこれをうまく処理するので、あまり役に立ちません。したがって、分岐予測に実際に影響を与えることはできないと思います。


2

OPに関しては、いいえ。GCCには、分岐が行われるか行われないと常にプロセッサに想定するように指示する方法はありません。あなたが持っているのは__builtin_expectで、これは他の人が言うことを実行します。さらに、分岐が常に行われるかどうかをプロセッサに伝えたくないと思います。Intelアーキテクチャなどの今日のプロセッサは、かなり複雑なパターンを認識し、効果的に適応できます。

ただし、デフォルトで分岐が予測されるかどうかの制御を想定したい場合があります。分岐統計に関してコードが「コールド」と呼ばれることがわかっている場合。

1つの具体的な例:例外管理コード。定義上、管理コードは例外的に発生しますが、おそらく最大のパフォーマンスが望まれる(できるだけ早く対処するための重大なエラーが発生する可能性がある)ため、デフォルトの予測を制御する必要があります。

別の例:入力を分類し、分類の結果を処理するコードにジャンプできます。多くの分類がある場合、プロセッサは統計を収集しますが、同じ分類がすぐに発生せず、予測リソースが最近呼び出されたコードに費やされるため、統計を失います。「このキャッシュに予測リソースを割り当てないでください」と言って、「これをキャッシュしないでください」と時々言うことができるプリミティブがあればいいのにと思います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.