「IF」は高価ですか?


98

私の人生では、教師がその日に正確に言ったことを思い出すことはできません。

モジュールは「データ構造とアルゴリズム」であり、彼は次のように何かを私たちに話しました:

if文は最も高価な[何か]です。[something]は[something]を登録します。

はい、私は恐ろしい記憶を持っています、そして本当に本当に申し訳ありませんが、私は何時間もグーグルで働いていて、何も起きていません。何か案は?


29
先生に頼むのは選択肢ですか?
マイケルマイヤーズ

7
先生にメールを送ってみませんか?彼らがその時にそこにいなかった場合(または教師自身がSOを読む場合)を除いて、SOの誰もがあなたの教師が言ったことを知っている可能性は低いです。
Bill Karwin、

11
そしてもちろん、義務的な鉄道の答え
ボボボボ2013年

ifステートメント、特にCの影響を受ける中括弧言語の "?:"式は、x86やarmプロセッサーなどの特別な条件付き実行命令によって実装できます。これらは、以前のテストに基づいて一部の操作を行うか行わないかの指示です。これらの優れた命令を使用すると、条件付きジャンプ/分岐/ 'goto'命令を完全に不要にすることができます。プログラムのフローを完全に予測可能にすることで、状況によっては大幅なパフォーマンスの向上が実現します。コード内のさまざまなポイントにジャンプする(予測できない場合もある)ことなく、まっすぐに進むだけです。
セシルワード

優れたコンパイラーは、コードを再編成し、式または?:式。自分のasmを本当に理解していて、たとえばAgner Fogの最適化ガイドを読んでいない限り、これをいじらないでください。コンパイラーは、ifステートメントまたは?:式が使用されます。
セシルワード

回答:


185

非常に低いレベル(ハードウェア)では、はい、sが高価であれば。理由を理解するには、パイプラインのしくみを理解する必要があります。

実行される現在の命令は、通常、命令ポインタ(IP)またはプログラムカウンタ(PC)と呼ばれるものに格納されます。これらの用語は同義ですが、異なるアーキテクチャでは異なる用語が使用されます。ほとんどの命令では、次の命令のPCは、現在のPCに現在の命令の長さを加えたものです。ほとんどのRISCアーキテクチャでは、命令はすべて一定の長さであるため、PCを一定量ずつ増やすことができます。x86などのCISCアーキテクチャの場合、命令は可変長になる可能性があるため、命令をデコードするロジックは、現在の命令が次の命令の場所を見つけるのにかかる時間を把握する必要があります。

以下のために分岐命令、ただし、次に実行する命令は、現在の命令の後の次の場所ではありません。分岐は後処理です。分岐はプロセッサに次の命令の場所を通知します。ブランチは条件付きまたは無条件のいずれかであり、ターゲットの場所は固定または計算のいずれかです。

条件付きと無条件の違いは簡単に理解できます。条件分岐は、特定の条件(ある数値が別の数値と等しいかどうかなど)が満たされた場合にのみ行われます。分岐が行われなかった場合、制御は通常のように分岐後の次の命令に進みます。無条件分岐の場合、分岐は常に行われます。条件付きブランチは、ifステートメントおよびforand whileループの制御テストに表示されます。無条件分岐は無限ループ、関数呼び出し、関数が戻る、に表示breakし、continue文、悪名高いgoto(これらのリストは網羅から遠く離れている)の文、および多く。

ブランチターゲットも重要な問題です。ほとんどのブランチには、固定ブランチターゲットがあります-コンパイル時に固定されたコード内の特定の場所に移動します。これには、ifステートメント、あらゆる種類のループ、通常の関数呼び出しなどが含まれます。 計算されたブランチは、実行時にブランチのターゲットを計算します。これには、switchステートメント(時々)、関数からの戻り、仮想関数呼び出し、および関数ポインター呼び出しが含まれます。

では、これはパフォーマンスにとってどのような意味があるのでしょうか。プロセッサは、パイプラインに分岐命令が表示されるのを見た場合、パイプラインを引き続き満たす方法を理解する必要があります。プログラムストリームの分岐の後にどの命令が来るかを理解するには、(1)分岐が行われるかどうか、および(2)分岐のターゲットという2つのことを知る必要があります。これを理解することは分岐予測と呼ばれ、難しい問題です。プロセッサーが正しく推測した場合、プログラムはフルスピードで続行します。代わりに、プロセッサーが誤って推測した場合、誤った計算に時間がかかっただけです。これで、パイプラインをフラッシュし、正しい実行パスからの命令で再ロードする必要があります。結論:大きなパフォーマンスヒット。

したがって、ifステートメントが高価になる理由は、分岐の予測ミスによるものです。これは最低レベルのみです。高レベルのコードを記述している場合は、これらの詳細についてまったく心配する必要はありません。Cまたはアセンブリで非常にパフォーマンスが重要なコードを記述している場合にのみ、これに注意する必要があります。その場合、さらにいくつかの命令が必要な場合でも、分岐のないコードを記述する方が、分岐するコードよりも優れていることがよくあります。、、などを計算するためabs()min()max()分岐せずに実行できるクールなビットトゥウィドルトリックがいくつかあります。


20
ブランチの予測ミスだけではありません。分岐はまた、コンパイラレベルで、またある程度はCPUレベルで(もちろん、順序が正しくないCPUの場合)、命令の並べ替えを禁止します。素敵な詳細な答えも。
2008年

5
高水準言語が最終的に低水準言語に翻訳され、非常にパフォーマンス中心のコードを記述している場合でも、ifステートメントを回避するコードを記述しても何も得られませんか?この概念は高級言語には適用されませんか?
c ..

18

if条件のコストも考慮に入れる必要があるため、「高額」は非常に相対的な用語であり、特に「」ステートメントとの関係で使用されます。その範囲は、いくつかの短いCPU命令から、リモートデータベースを呼び出す関数の結果のテストまで、さまざまです。

気にしない。組み込みプログラミングをしているのでない限り、おそらく " if" のコストを気にする必要はありません。ほとんどのプログラマにとって、これはアプリのパフォーマンスを向上させる要因にはなりません。


1
間違いなく相対的... cmp / cond jmpは、多くのプロセッサのmulよりも高速です。
Brian Knoblauch、

4
はい、私はそれについて心配するべきではないことに同意します。ここでは何も最適化しようとはしていません。私はただ見つけて学ぼうとしています。;)
pek

15

特にRISCアーキテクチャのマイクロプロセッサでの分岐は、最も高価な命令の一部です。これは、多くのアーキテクチャーで、コンパイラーは実行の可能性が最も高いパスを予測し、それらの命令を実行可能ファイルの次に配置するため、分岐が発生したときにそれらがすでにCPUキャッシュにあるためです。ブランチが逆の場合、メインメモリに戻り、新しい命令をフェッチする必要があります。これはかなりコストがかかります。多くのRISCアーキテクチャでは、分岐を除いてすべての命令が1サイクルです(多くの場合、2サイクルです)。ここでは主要なコストについて話しているわけではないので、心配しないでください。また、コンパイラーは99%の時間よりも最適化します。)EPICアーキテクチャ(Itaniumは一例です)の本当に素晴らしい点の1つは、ブランチの両側から命令をキャッシュ(および処理を開始)し、ブランチの結果が得られたら不要なセットを破棄することです。知られている。これにより、予期しないパスに沿って分岐した場合に、一般的なアーキテクチャの余分なメモリアクセスを節約できます。


13

セルのパフォーマンスに関するブランチ排除によるパフォーマンス向上の記事をご覧ください。もう1つの楽しいのは、リアルタイム衝突検出ブログのブランチなしの選択に関するこの投稿です。

この質問への回答としてすでに投稿されている優れた回答に加えて、 "if"ステートメントは高価な低レベルの操作と見なされていますが、より高いレベルの環境でブランチフリープログラミング手法を利用しようとしていることに注意してくださいスクリプト言語やビジネスロジックレイヤー(言語に関係なく)などは、とんでもなく不適切な場合があります。

ほとんどの場合、プログラムは最初に明確にするために記述し、次にパフォーマンスを最適化する必要があります。パフォーマンスが最も重要である多くの問題ドメインがありますが、簡単な事実は、ほとんどの開発者がレンダリングエンジンのコアの奥深くで使用するモジュールや、何週間も実行される高性能流体力学シミュレーションを作成していないことです。ソリューションが最優先事項である「単に機能する」ことである場合、最後に頭に浮かぶのは、コード内の条件ステートメントのオーバーヘッドを節約できるかどうかです。


確かに!また、呼び出しを促進する言語(基本的に、アセンブラーまたはstdlibなしのC以外)でコーディングすると、通常のプログラミング手法によるパイプラインの干渉により、条件付き分岐に関する疑問が解消されることも付け加えられます。
ロスパターソン、

10

ifそれ自体は遅くありません。スローネスは常に相対的なものであり、ifステートメントの "オーバーヘッド"を感じたことはありません。高性能のコードを作成する場合は、とにかく分岐を避けたいと思うでしょう。何がif遅いことは、プロセッサが後からコードをプリロードされていることでif、いくつかのヒューリスティックやその他もろもろに基づきます。またif、プロセッサはどのパスをたどるかがまだわからないため、パイプラインマシンコードの分岐命令の直後にパイプラインがコードを実行するのを停止します(パイプラインプロセッサでは、複数の命令がインターリーブされて実行されます)。実行されたコードは逆に実行する必要があるかもしれません(他の分岐が行われた場合、それはと呼ばれますbranch mispredictionnoop

場合はif悪である、そしてswitch悪すぎです、そして&&||あまりにも。心配しないでください。


7

可能な最低レベルでif構成されます(特定のすべてのアプリ固有の前提条件を計算した後if):

  • いくつかのテスト命令
  • テストが成功した場合はコードのどこかにジャンプし、そうでない場合は先に進みます。

それに関連する費用:

  • 低レベルの比較-通常1 CPUの動作、超安価
  • 潜在的なジャンプ-これは高価になる可能性があります

ジャンプが高価な理由:

  • CPUによってキャッシュされていないことが判明した場合は、メモリ内の任意の場所にある任意のコードにジャンプできます-遅いメインメモリにアクセスする必要があるため、問題があります。
  • 最近のCPUは分岐予測を行います。彼らは成功するかどうかを推測し、パイプラインの前でコードを実行しようとするので、スピードアップします。予測が失敗した場合、パイプラインによって先に実行されたすべての計算を無効にする必要があります。これも高価な操作です

要約すると:

  • あなたが本当に、本当に、本当に気にかけているなら、パフォーマンスに気を配ることができます。
  • リアルタイムレイトレーサーや生物学的シミュレーションなどを作成している場合にのみ、これに注意する必要があります。現実の世界のほとんどでそれを気にする理由はありません。

これを次のレベルに進めます:ネストされたおよび/または複合ifステートメントについてはどうですか?誰かがこのようなifステートメントをたくさん書くと、費用はすぐにかなり顕著になります。また、ほとんどの開発者にとって、ステートメントがそのような基本的な操作のように見える場合、複雑な条件付き分岐を回避することは、多くの場合、文体上の懸念に追いやられます。文体的な懸念は依然として重要ですが、多くの場合、それらは無視されるべき最初の懸念となる可能性があります。
ジェイデル

7

最近のプロセッサには長い実行パイプラインがあり、複数の命令がさまざまなステージで同時に実行されます。次の命令の実行が開始されたときに、ある命令の結果が常にわかるとは限りません。条件付きジャンプが実行されると(if)、パイプラインが空になるまで待機してから、命令ポインターの方向を知る必要があります。

長い貨物列車だと思います。直線的に多くの貨物を速く運ぶことができますが、それはひどく角を曲がります。

Pentium 4(プレスコット)には、31ステージの有名な長いパイプラインがありました。

ウィキペディアの詳細


3
貨物列車のメタファーの+1-次回は、プロセッサパイプラインについて説明する必要があることを覚えておきます。
ダニエル・プライデン2009

6

たぶん、分岐はCPU命令のプリフェッチを殺しますか?


私の「リサーチ」で、ジャンプテーブルとスイッチステートメントの分岐について学びましたが、ifステートメントについては何も知りませんでした。それについて少し詳しく説明してもらえますか?
pek

IIRC、CPUは通常、単一の推定実行パスに沿って命令をプリフェッチしていますが、予測された実行パスから分岐する「if」ステートメントは、プリフェッチされた命令を無効にし、プリテクティングを再起動する必要があります。
activout.se 2008年

適切なプロセッサには、分岐が行われるかどうかを推測しようとする分岐予測機能と、予測に基づいて命令をプリフェッチする必要があります(これは一般に非常に優れています)。GCCには、プログラマーが分岐予測子にヒントを提供できるようにするC拡張機能さえあります。
mipadi 2008年

2
さらに、CPUは通常、先読みする命令を(プリフェッチするだけでなく)早く実行することを先取りし、コンパイラーは命令を並べ替えようとしますが、これはブランチ間で危険になり、ブランチが多すぎると命令スケジューリングを強制終了できます。これはパフォーマンスを低下させます。
2008年

6

また、ループの内側必ずしもそれほど高価ではないことに注意してください。

最近のCPUは、ifステートメントの最初の訪問時に、「if-body」が取得されると想定します(または、逆に言えば、ループ本体が複数回取得されることも想定しています)(*)。2回目以降のアクセスでは、それ(CPU)はブランチ履歴テーブルを調べることができますを調べて、状態が最後にどのようになっていたかを確認できます(それは真でしたか、それとも偽でしたか?)。前回falseだった場合、投機的実行はifの「else」に進むか、ループを超えます。

(*)ルールは、実際には「前方分岐は行われず、後方分岐は行われる」です。if文では、そこにあるだけ(ポイントへ[前進]ジャンプであれば、体の後 falseに条件評価された場合)は、(覚えている:CPUはとにかく分岐/ジャンプを取らないために想定している)が、ループ内、ループの後の位置への前方分岐(取得されない)と、反復時の後方分岐(取得される)が存在する可能性があります。

これは、仮想関数の呼び出しまたは関数ポインター呼び出しが、多くの人が想定しているほど悪くない理由の1つでもあります(http://phresnel.org/blog/


5

多くの人が指摘しているように、現代のコンピュータでは条件付きブランチは非常に遅い場合があります。

とは言っても、ifステートメントに含まれない条件付きブランチはたくさんあります。コンパイラーが何を考え出すかを常に知ることはできず、基本的なステートメントにかかる時間を心配することは事実上常に間違っています。する。(コンパイラーが確実に生成するものを特定できる場合は、適切な最適化コンパイラーがない可能性があります。)


4

これが参照していると私が想像できる唯一のことは、ifステートメントが一般に分岐をもたらす可能性があるという事実です。プロセッサアーキテクチャの詳細によっては、分岐によってパイプラインが停止するなど、最適な状況が得られない場合があります。

ただし、これは状況によって非常に異なります。最近のほとんどのプロセッサには、分岐の悪影響を最小限に抑えるための分岐予測機能があります。別の例は、ARMアーキテクチャ(およびおそらく他のアーキテクチャ)が条件付きロジックを処理する方法です。ARMには命令レベルの条件付き実行があるため、単純な条件付きロジックでは分岐が発生しません。条件が満たされない場合、命令は単にNOPとして実行されます。

以上のことをすべて言っておきます-このことについて心配する前にロジックを正しくしてください。不正なコードは、可能な限り最適化されていません。


ARMの条件付き命令はILPを阻害するため、問題が発生する可能性があると聞きました。
JD

3

CPUは深くパイプライン化されています。分岐命令(if / for / while / switch / etc)は、CPUが次にロードして実行する命令を実際に認識していないことを意味します。

CPUは、何をすべきかを知るのを待っている間にストールするか、CPUが推測します。古いCPUの場合、または推測が間違っている場合は、正しい命令をロードしてロードする間、パイプラインが停止する必要があります。CPUによっては、10〜20命令分のストールが発生する可能性があります。

最近のCPUは、適切な分岐予測を行い、同時に複数のパスを実行して実際のパスのみを維持することで、これを回避しようとしています。これは非常に役立ちますが、これまでのところしか実行できません。

クラスで頑張ってください。

また、実際にこれについて心配する必要がある場合は、OS設計、リアルタイムグラフィックス、科学計算、または同様にCPUに依存する何かを行っていることでしょう。心配する前にプロファイル。


2

プログラムは、明らかに非効率的ではない、最も明確で最も単純でクリーンな方法で記述してください。これは、最も高価なリソースを最大限に活用します。プログラムを作成するか、後でデバッグする(理解が必要)か。パフォーマンスが十分でない場合、測定しますボトルネックがどこにあるか、そしてそれらを軽減する方法を見てください。非常にまれな場合にのみ、その際に個々の(ソース)指示について心配する必要があります。パフォーマンスとは、最初の行で適切なアルゴリズムとデータ構造を選択し、注意深くプログラミングして、十分に高速なマシンを取得することです。優れたコンパイラーを使用すると、最新のコンパイラーが再構成するコードの種類を見ると驚くでしょう。パフォーマンスのためにコードを再構築することは、一種の最後の手段です。コードはより複雑になり(したがってバグが多く)、変更が難しくなるため、全体的にコストが高くなります。


1

一部のCPU(X86など)は、そのような分岐予測待ち時間を回避するために、プログラミングレベルに分岐予測を提供します。

一部のコンパイラーは、(GCCのように)これらを(C / C ++のような)より高いレベルのプログラミング言語への拡張として公開します。

LinuxカーネルのLike()/ Unlikely()マクロを参照してください-どのように機能しますか?彼らの利点は何ですか?


0

私は一度、友人とこの議論をしました。彼は非常にナイーブなサークルアルゴリズムを使用していましたが、自分よりも高速であると主張しました(サークルの1/8しか計算しない種類)。最後に、ifステートメントはsqrtに置き換えられ、どういうわけかそれはより高速でした。おそらく、FPUにはsqrtが組み込まれているからでしょうか?


-1

ALUの使用の点で最も高価ですか?比較対象の値を格納するためにCPUレジスタを使い果たし、ifステートメントが実行されるたびに値をフェッチして比較するのに時間がかかります。

したがって、その最適化は、ループが実行される前に1つの比較を行い、結果を変数として格納することです。

あなたの行方不明の言葉を解釈しようとしているだけです。

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