この投稿 (StackOverflow の回答) (最適化セクション)を読んだ後、なぜ条件付き移動が分岐予測失敗に対して脆弱ではないのか疑問に思いました。cond move here (PDF by AMD) に関する記事を見つけました。また、彼らは cond のパフォーマンス上の利点を主張しています。動きます。しかし、これはなぜですか?見えません。その ASM 命令が評価される時点では、先行する CMP 命令の結果はまだわかりません。
5 に答える
分岐の予測ミスはコストがかかる
最新のプロセッサは、一般に、問題がなければサイクルごとに 1 ~ 3 個の命令を実行します (これらの命令のデータ依存関係が前の命令またはメモリから到着するのを待ってストールしない場合)。
上記のステートメントは、タイトなループには驚くほどうまく当てはまりますが、これは、サイクルが来たときに命令が実行されるのを妨げる可能性のある追加の依存関係を盲目にするべきではありません: 命令が実行されるためには、プロセッサがフェッチとデコードを開始している必要があります。 15 ~ 20 サイクル前です。
プロセッサが分岐に遭遇したとき、プロセッサは何をすべきか? 両方のターゲットのフェッチとデコードはスケーリングしません (より多くの分岐が続く場合、指数関数的な数のパスを並行してフェッチする必要があります)。そのため、プロセッサは 2 つのブランチのうちの 1 つだけを投機的にフェッチしてデコードします。
これが、予測ミスによる分岐が高くつく理由です。効率的な命令パイプラインのために、通常は目に見えない 15 ~ 20 サイクルのコストがかかります。
条件付き移動は非常に高価になることはありません
条件付きの移動は予測を必要としないため、このペナルティを受けることはありません。通常の命令と同様に、データの依存関係があります。実際、条件付き移動には、通常の命令よりも多くのデータ依存性があります。これは、データ依存性には「条件 true」と「条件 false」の両方のケースが含まれるためです。r1
条件付きで に移動する命令の後、r2
の内容はの前の値とr2
の両方に依存しているように見えます。適切に予測された条件分岐により、プロセッサはより正確な依存関係を推測できます。ただし、データの依存関係が到着するまでに時間がかかる場合でも、通常、到着するまでに 1 ~ 2 サイクルかかります。r2
r1
メモリからレジスタへの条件付きの移動は、危険な賭けになる場合があることに注意してください。メモリから読み取った値がレジスタに割り当てられていないという条件の場合、メモリを何も待たずに待機したことになります。しかし、命令セットで提供される条件付き移動命令は、通常、レジスターからレジスターへのレジスターであり、プログラマー側でのこの間違いを防ぎます。
命令パイプラインがすべてです。最新の CPU はパイプラインで命令を実行することを思い出してください。これにより、CPU が実行フローを予測できる場合、パフォーマンスが大幅に向上します。
cmov
add eax, ebx
cmp eax, 0x10
cmovne ebx, ecx
add eax, ecx
その ASM 命令が評価される時点では、先行する CMP 命令の結果はまだわかりません。
おそらくですが、CPU は、 and命令cmov
の結果に関係なく、 に続く命令が直後に実行されることを認識しています。したがって、次の命令は事前に安全にフェッチ/デコードできますが、分岐の場合はそうではありません。cmp
cmov
次の命令は、実行する前に実行することもできcmov
ます (私の例では、これは安全です)。
ブランチ
add eax, ebx
cmp eax, 0x10
je .skip
mov ebx, ecx
.skip:
add eax, ecx
この場合、CPU のデコーダーが認識したときに、 je .skip
1) 次の命令から、または 2) ジャンプ ターゲットから、命令のプリフェッチ/デコードを続行するかどうかを選択する必要があります。CPU は、この前方条件分岐が発生しないと推測するため、次の命令mov ebx, ecx
がパイプラインに入ります。
数サイクル後、je .skip
が実行され、分岐が実行されます。やばい!私たちのパイプラインは、決して実行してはならないランダムなジャンクを保持しています。CPU は、キャッシュされたすべての命令をフラッシュし、最初からやり直す必要があり.skip:
ます。
cmov
これは、実行フローを変更しないため、分岐の予測ミスによるパフォーマンスの低下です。
実際、結果はまだわからないかもしれませんが、他の状況 (特に依存関係の連鎖) が許せば、CPU は に続く命令を並べ替えて実行できますcmov
。分岐が含まれていないため、これらの命令はいずれの場合も評価する必要があります。
次の例を検討してください。
cmoveq edx, eax
add ecx, ebx
mov eax, [ecx]
に続く 2 つの命令はcmov
、 の結果に依存しないため、それ自体が保留中cmov
でも実行できます(これをアウト オブ オーダー実行と呼びます)。実行できない場合でも、フェッチしてデコードできます。cmov
分岐バージョンは次のようになります。
jne skip
mov edx, eax
skip:
add ecx, ebx
mov eax, [ecx]
ここでの問題は、制御フローが変化しており、CPU が、分岐が行われたと誤って予測された場合に、スキップされた命令を単に「挿入」できることを確認できるほど賢くないことです。mov
代わりに、分岐後に行ったすべてを破棄し、再起動します。最初から。ここからペナルティが発生します。
これらを読むべきです。Fog+Intel では、CMOV を検索するだけです。
Linus Torvald の 2007 年頃の CMOV に対する批判
Agner Fog のマイクロアーキテクチャーの比較
Intel® 64 および IA-32 アーキテクチャー最適化リファレンス マニュアル
短い答え、正しい予測は「無料」ですが、条件付き分岐の予測ミスはHaswellで14〜20サイクルかかる可能性があります。ただし、CMOV は無料ではありません。それでも、トーバルズが怒鳴ったときよりも、CMOV は今の方がはるかに優れていると思います。これまでに回答したすべてのプロセッサで常に正しいものは 1 つもありません。