web-dev-qa-db-ja.com

ブランチを予測する可能性をブランチプレディクタに伝えることは可能ですか?

明確にするために、ここではいかなる種類の移植性も必要としないので、特定のボックスに結び付ける解決策は問題ありません。

基本的に、99%の時間でtrueと評価されるifステートメントがあり、パフォーマンスの最後のクロックをすべて実行しようとしている場合、ある種のコンパイラコマンドを発行できます(GCC 4.1.2とx86 ISAを使用して、それは重要です)ブランチプレディクタにそのブランチのためにキャッシュする必要があることを伝えるために?

73
Andy Shulman

はい。 http://kerneltrap.org/node/4705

__builtin_expectは、gcc(バージョン> = 2.96)がプログラマに分岐予測情報をコンパイラーに示すために提供するメソッドです。 __builtin_expectの戻り値は、渡される最初の引数(整数のみの場合もある)です。

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

     [This] would indicate that we do not expect to call `foo', since we
     expect `x' to be zero. 
57
Drakosha

はい、ただしnoの影響があります。例外はNetburstより前の古い(廃止された)アーキテクチャであり、それでも測定可能なことは何もしません。

IntelがNetburstアーキテクチャで導入した「分岐ヒント」オペコードと、一部の古いアーキテクチャでは、コールドジャンプのデフォルトの静的分岐予測(後方予測が行われ、前方予測が行われない)があります。 GCCはこれを__builtin_expect (x, prediction)で実装します。ここで、予測は通常0または1です。コンパイラが発行するオペコードは、すべての新しいバージョンでignoredですプロセッサのアーキテクチャ(> =コア2)。これが実際に何かを行う小さなコーナーケースは、古いNetburstアーキテクチャのコールドジャンプのケースです。 Intelは、おそらく静的ブランチヒントを使用しないことをお勧めします。これは、コードサイズの増加が、可能な限りの高速化よりも有害であると考えているためです。

予測子の役に立たない分岐のヒントに加えて、__builtin_expectはその使用法を持っています。コンパイラは、キャッシュの使用を改善したりメモリを節約するためにコードを並べ替えます。

期待どおりに動作しない理由はいくつかあります。

  • プロセッサは小さなループ(n <64)を完全に予測できます。
  • プロセッサは小さな繰り返しパターン(n〜7)を完全に予測できます。
  • プロセッサー自体は、コンパイル時のコンパイラー/プログラマーよりも実行時の分岐の確率を推定できます。
  • 分岐の予測可能性(=分岐が正しく予測される確率)は、分岐が行われる確率よりもはるかに重要です。残念ながら、これは非常にアーキテクチャに依存しており、ブランチの予測可能性を予測することは悪名高いほど困難です。

Agner Fogs manuals で、分岐予測の内部作業の詳細を読んでください。 gcc メーリングリスト も参照してください。

71
Gunther Piez

Pentium 4(別名Netburstマイクロアーキテクチャー)には、jcc命令のプレフィックスとしてブランチ予測子のヒントがありましたが、P4だけがこれに対して何かを行いました。 http://ref.x86asm.net/geek32.html を参照してください。 http://www.agner.org/optimize/Agner Fogの優れたasm optガイドのセクション3.5 彼はC++での最適化のガイドも持っています。

以前およびそれ以降のx86 CPUは、これらのプレフィックスバイトを暗黙的に無視します。 可能性のある/可能性の低いヒントの使用に関するパフォーマンステスト結果はありますか? PowerPCには、エンコードの一部として分岐予測ヒントを持ついくつかのジャンプ命令があると述べています。それはかなり珍しい建築上の特徴です。コンパイル時にブランチを静的に予測することは正確に行うのが非常に難しいため、通常はそれをハードウェアに任せて理解することをお勧めします。

最新のIntelおよびAMD CPUの分岐予測子と分岐ターゲットバッファーがどのように動作するかについては、公式にはあまり公開されていません。最適化マニュアル(AMDとIntelのWebサイトで簡単に見つけることができます)はいくつかのアドバイスを提供しますが、特定の動作を文書化しません。一部の人々は、実装を神聖化しようとするためにテストを実行しました。 Core2が持つBTBエントリの数...とにかく、予測変数を明示的に示唆するという考えは(今のところ)放棄されています。

文書化されているのは、たとえば、ループが常に一定の短い反復数(<8または16 IIRC)を実行する場合、Core2にループ履歴の予測ミスを回避できる分岐履歴バッファーがあることです。しかし、64バイト(またはPenrynの19uops)に収まるループはバッファから再生されるため、命令フェッチのボトルネックがないため、展開が速すぎてはいけません... Agner FogのPDFを読んでくださいexcellent

参照 なぜIntelはここ数年で静的分岐予測メカニズムを変更したのですか? :Intelは、リバースエンジニアリングを試みたパフォーマンス実験からわかる限り、Sandybridgeは静的予測をまったく使用していません。 CPUの機能。 (多くの古いCPUには、動的予測が失敗した場合のフォールバックとして静的予測があります。通常の静的予測は、前方分岐は行われず、後方分岐が行われます(後方分岐はループ分岐であることが多いため)。


GNU Cの___builtin_expect_を使用したlikely()/unlikely()マクロの効果(ドラコシャの回答の言及のように) )はnotを直接BPヒントをasmに挿入します(_gcc -march=pentium4_で可能性がありますが、他に何か)。

実際の効果は、高速パスの分岐が少なくなり、命令の総数が少なくなるようにコードをレイアウトすることです。これは、静的予測が機能する場合(例:動的予測子がコールドで、予測子キャッシュでブランチを互いにエイリアスさせるのではなく、静的予測にフォールバックするCPUでコールドになる)での分岐予測に役立ちます。

Code-genの具体例については、 if elseステートメントでのGCCの__builtin_expectの利点は何ですか? を参照してください。

完全に予測されたとしても、採用されたブランチは採用されなかったブランチよりもわずかにコストがかかります。 CPUが16バイトのチャンクでコードをフェッチして並列にデコードする場合、分岐は、そのフェッチブロック内の後続の命令が実行される命令ストリームの一部ではないことを意味します。フロントエンドにバブルを作成し、高スループットコードのボトルネックになる可能性があります(キャッシュミス時にバックエンドでストールせず、命令レベルの並列性が高くなります)。

異なるブロック間でジャンプすると、コードのより多くのキャッシュラインに影響を与える可能性があり、L1iキャッシュのフットプリントが増加し、寒い場合は命令キャッシュミスが増える可能性があります。 (そして潜在的にuop-cacheフットプリント)。したがって、高速パスを短く線形にすることのもう1つの利点です。


GCCのプロファイルに基づく最適化により、通常はマクロの可能性/可能性の低いマクロが不要になります。コンパイラーは、各ブランチがコードレイアウトの決定を行った方法に関するランタイムデータを収集し、ホットブロックとコールドブロック/関数を識別します。 (たとえば、コールド関数ではなく、ホット関数でループを展開します。)_-fprofile-generate_および_-fprofile-use_ GCCマニュアル を参照してください。 g ++でプロファイルに基づく最適化を使用するには?

そうでない場合、マクロの可能性が高い/そうでない、PGOを使用していない場合、GCCはさまざまなヒューリスティックを使用して推測する必要があります。 _-fguess-branch-probability_は、_-O1_以降ではデフォルトで有効になっています。

https://www.phoronix.com/scan.php?page=article&item=gcc-82-pgo&num=1 XeonスケーラブルサーバーCPU上のgcc8.2でのPGOと通常のベンチマーク結果。 (Skylake-AVX512)。すべてのベンチマークで少なくともわずかな速度向上が見られ、一部では約10%の向上が見られました。 (そのほとんどは、おそらくホットループでのループのアンロールによるものですが、一部はおそらく、より優れたブランチレイアウトやその他の効果によるものです。)

31
Peter Cordes

分岐予測について心配するのではなく、コードをプロファイリングしてコードを最適化し、分岐数を減らすことをお勧めします。 1つの例はループの展開であり、もう1つの例はifステートメントを使用するのではなく、ブールプログラミング手法を使用しています。

ほとんどのプロセッサはステートメントをプリフェッチするのが大好きです。通常、分岐ステートメントは、 fault プロセッサ内でプリフェッチキューをフラッシュさせます。これが最大のペナルティです。このペナルティ時間を削減するには、使用できるブランチが少なくなるようにコードを書き直し(および設計して)ください。また、一部のプロセッサは分岐せずに条件付きで命令を実行できます。

ループの展開と大きなI/Oバッファーを使用して、プログラムを1時間の実行時間から2分に最適化しました。この場合、分岐予測は時間の節約にはなりませんでした。

6
Thomas Matthews

Sun C Studioには、このケース用に定義されたプラグマがいくつかあります。

#pragma rarely_called()

これは、条件式の一部が関数呼び出しであるか、関数呼び出しで始まる場合に機能します。

しかし、一般的なif/whileステートメントにタグを付ける方法はありません

1
Lothar