私の人生では、教師がその日に正確に言ったことを思い出すことはできません。
モジュールは「データ構造とアルゴリズム」であり、彼は次のように何かを私たちに話しました:
if
文は最も高価な[何か]です。[something]は[something]を登録します。
はい、私は恐ろしい記憶を持っています、そして本当に本当に申し訳ありませんが、私は何時間もグーグルで働いていて、何も起きていません。何か案は?
私の人生では、教師がその日に正確に言ったことを思い出すことはできません。
モジュールは「データ構造とアルゴリズム」であり、彼は次のように何かを私たちに話しました:
if
文は最も高価な[何か]です。[something]は[something]を登録します。
はい、私は恐ろしい記憶を持っています、そして本当に本当に申し訳ありませんが、私は何時間もグーグルで働いていて、何も起きていません。何か案は?
回答:
非常に低いレベル(ハードウェア)では、はい、sが高価であれば。理由を理解するには、パイプラインのしくみを理解する必要があります。
実行される現在の命令は、通常、命令ポインタ(IP)またはプログラムカウンタ(PC)と呼ばれるものに格納されます。これらの用語は同義ですが、異なるアーキテクチャでは異なる用語が使用されます。ほとんどの命令では、次の命令のPCは、現在のPCに現在の命令の長さを加えたものです。ほとんどのRISCアーキテクチャでは、命令はすべて一定の長さであるため、PCを一定量ずつ増やすことができます。x86などのCISCアーキテクチャの場合、命令は可変長になる可能性があるため、命令をデコードするロジックは、現在の命令が次の命令の場所を見つけるのにかかる時間を把握する必要があります。
以下のために分岐命令、ただし、次に実行する命令は、現在の命令の後の次の場所ではありません。分岐は後処理です。分岐はプロセッサに次の命令の場所を通知します。ブランチは条件付きまたは無条件のいずれかであり、ターゲットの場所は固定または計算のいずれかです。
条件付きと無条件の違いは簡単に理解できます。条件分岐は、特定の条件(ある数値が別の数値と等しいかどうかなど)が満たされた場合にのみ行われます。分岐が行われなかった場合、制御は通常のように分岐後の次の命令に進みます。無条件分岐の場合、分岐は常に行われます。条件付きブランチは、if
ステートメントおよびfor
and while
ループの制御テストに表示されます。無条件分岐は無限ループ、関数呼び出し、関数が戻る、に表示break
し、continue
文、悪名高いgoto
(これらのリストは網羅から遠く離れている)の文、および多く。
ブランチターゲットも重要な問題です。ほとんどのブランチには、固定ブランチターゲットがあります-コンパイル時に固定されたコード内の特定の場所に移動します。これには、if
ステートメント、あらゆる種類のループ、通常の関数呼び出しなどが含まれます。 計算されたブランチは、実行時にブランチのターゲットを計算します。これには、switch
ステートメント(時々)、関数からの戻り、仮想関数呼び出し、および関数ポインター呼び出しが含まれます。
では、これはパフォーマンスにとってどのような意味があるのでしょうか。プロセッサは、パイプラインに分岐命令が表示されるのを見た場合、パイプラインを引き続き満たす方法を理解する必要があります。プログラムストリームの分岐の後にどの命令が来るかを理解するには、(1)分岐が行われるかどうか、および(2)分岐のターゲットという2つのことを知る必要があります。これを理解することは分岐予測と呼ばれ、難しい問題です。プロセッサーが正しく推測した場合、プログラムはフルスピードで続行します。代わりに、プロセッサーが誤って推測した場合、誤った計算に時間がかかっただけです。これで、パイプラインをフラッシュし、正しい実行パスからの命令で再ロードする必要があります。結論:大きなパフォーマンスヒット。
したがって、ifステートメントが高価になる理由は、分岐の予測ミスによるものです。これは最低レベルのみです。高レベルのコードを記述している場合は、これらの詳細についてまったく心配する必要はありません。Cまたはアセンブリで非常にパフォーマンスが重要なコードを記述している場合にのみ、これに注意する必要があります。その場合、さらにいくつかの命令が必要な場合でも、分岐のないコードを記述する方が、分岐するコードよりも優れていることがよくあります。、、などを計算するためabs()
にmin()
、max()
分岐せずに実行できるクールなビットトゥウィドルトリックがいくつかあります。
if
条件のコストも考慮に入れる必要があるため、「高額」は非常に相対的な用語であり、特に「」ステートメントとの関係で使用されます。その範囲は、いくつかの短いCPU命令から、リモートデータベースを呼び出す関数の結果のテストまで、さまざまです。
気にしない。組み込みプログラミングをしているのでない限り、おそらく " if
" のコストを気にする必要はありません。ほとんどのプログラマにとって、これはアプリのパフォーマンスを向上させる要因にはなりません。
特にRISCアーキテクチャのマイクロプロセッサでの分岐は、最も高価な命令の一部です。これは、多くのアーキテクチャーで、コンパイラーは実行の可能性が最も高いパスを予測し、それらの命令を実行可能ファイルの次に配置するため、分岐が発生したときにそれらがすでにCPUキャッシュにあるためです。ブランチが逆の場合、メインメモリに戻り、新しい命令をフェッチする必要があります。これはかなりコストがかかります。多くのRISCアーキテクチャでは、分岐を除いてすべての命令が1サイクルです(多くの場合、2サイクルです)。ここでは主要なコストについて話しているわけではないので、心配しないでください。また、コンパイラーは99%の時間よりも最適化します。)EPICアーキテクチャ(Itaniumは一例です)の本当に素晴らしい点の1つは、ブランチの両側から命令をキャッシュ(および処理を開始)し、ブランチの結果が得られたら不要なセットを破棄することです。知られている。これにより、予期しないパスに沿って分岐した場合に、一般的なアーキテクチャの余分なメモリアクセスを節約できます。
セルのパフォーマンスに関するブランチ排除によるパフォーマンス向上の記事をご覧ください。もう1つの楽しいのは、リアルタイム衝突検出ブログのブランチなしの選択に関するこの投稿です。
この質問への回答としてすでに投稿されている優れた回答に加えて、 "if"ステートメントは高価な低レベルの操作と見なされていますが、より高いレベルの環境でブランチフリープログラミング手法を利用しようとしていることに注意してくださいスクリプト言語やビジネスロジックレイヤー(言語に関係なく)などは、とんでもなく不適切な場合があります。
ほとんどの場合、プログラムは最初に明確にするために記述し、次にパフォーマンスを最適化する必要があります。パフォーマンスが最も重要である多くの問題ドメインがありますが、簡単な事実は、ほとんどの開発者がレンダリングエンジンのコアの奥深くで使用するモジュールや、何週間も実行される高性能流体力学シミュレーションを作成していないことです。ソリューションが最優先事項である「単に機能する」ことである場合、最後に頭に浮かぶのは、コード内の条件ステートメントのオーバーヘッドを節約できるかどうかです。
if
それ自体は遅くありません。スローネスは常に相対的なものであり、ifステートメントの "オーバーヘッド"を感じたことはありません。高性能のコードを作成する場合は、とにかく分岐を避けたいと思うでしょう。何がif
遅いことは、プロセッサが後からコードをプリロードされていることでif
、いくつかのヒューリスティックやその他もろもろに基づきます。またif
、プロセッサはどのパスをたどるかがまだわからないため、パイプラインマシンコードの分岐命令の直後にパイプラインがコードを実行するのを停止します(パイプラインプロセッサでは、複数の命令がインターリーブされて実行されます)。実行されたコードは逆に実行する必要があるかもしれません(他の分岐が行われた場合、それはと呼ばれますbranch misprediction
)noop
。
場合はif
悪である、そしてswitch
悪すぎです、そして&&
、||
あまりにも。心配しないでください。
可能な最低レベルでif
構成されます(特定のすべてのアプリ固有の前提条件を計算した後if
):
それに関連する費用:
ジャンプが高価な理由:
要約すると:
最近のプロセッサには長い実行パイプラインがあり、複数の命令がさまざまなステージで同時に実行されます。次の命令の実行が開始されたときに、ある命令の結果が常にわかるとは限りません。条件付きジャンプが実行されると(if)、パイプラインが空になるまで待機してから、命令ポインターの方向を知る必要があります。
長い貨物列車だと思います。直線的に多くの貨物を速く運ぶことができますが、それはひどく角を曲がります。
Pentium 4(プレスコット)には、31ステージの有名な長いパイプラインがありました。
ウィキペディアの詳細
たぶん、分岐はCPU命令のプリフェッチを殺しますか?
また、ループの内側は必ずしもそれほど高価ではないことに注意してください。
最近のCPUは、ifステートメントの最初の訪問時に、「if-body」が取得されると想定します(または、逆に言えば、ループ本体が複数回取得されることも想定しています)(*)。2回目以降のアクセスでは、それ(CPU)はブランチ履歴テーブルを調べることができますを調べて、状態が最後にどのようになっていたかを確認できます(それは真でしたか、それとも偽でしたか?)。前回falseだった場合、投機的実行はifの「else」に進むか、ループを超えます。
(*)ルールは、実際には「前方分岐は行われず、後方分岐は行われる」です。if文では、そこにあるだけ(ポイントへ[前進]ジャンプであれば、体の後 falseに条件評価された場合)は、(覚えている:CPUはとにかく分岐/ジャンプを取らないために想定している)が、ループ内、ループの後の位置への前方分岐(取得されない)と、反復時の後方分岐(取得される)が存在する可能性があります。
これは、仮想関数の呼び出しまたは関数ポインター呼び出しが、多くの人が想定しているほど悪くない理由の1つでもあります(http://phresnel.org/blog/)
これが参照していると私が想像できる唯一のことは、if
ステートメントが一般に分岐をもたらす可能性があるという事実です。プロセッサアーキテクチャの詳細によっては、分岐によってパイプラインが停止するなど、最適な状況が得られない場合があります。
ただし、これは状況によって非常に異なります。最近のほとんどのプロセッサには、分岐の悪影響を最小限に抑えるための分岐予測機能があります。別の例は、ARMアーキテクチャ(およびおそらく他のアーキテクチャ)が条件付きロジックを処理する方法です。ARMには命令レベルの条件付き実行があるため、単純な条件付きロジックでは分岐が発生しません。条件が満たされない場合、命令は単にNOPとして実行されます。
以上のことをすべて言っておきます-このことについて心配する前にロジックを正しくしてください。不正なコードは、可能な限り最適化されていません。
CPUは深くパイプライン化されています。分岐命令(if / for / while / switch / etc)は、CPUが次にロードして実行する命令を実際に認識していないことを意味します。
CPUは、何をすべきかを知るのを待っている間にストールするか、CPUが推測します。古いCPUの場合、または推測が間違っている場合は、正しい命令をロードしてロードする間、パイプラインが停止する必要があります。CPUによっては、10〜20命令分のストールが発生する可能性があります。
最近のCPUは、適切な分岐予測を行い、同時に複数のパスを実行して実際のパスのみを維持することで、これを回避しようとしています。これは非常に役立ちますが、これまでのところしか実行できません。
クラスで頑張ってください。
また、実際にこれについて心配する必要がある場合は、OS設計、リアルタイムグラフィックス、科学計算、または同様にCPUに依存する何かを行っていることでしょう。心配する前にプロファイル。
プログラムは、明らかに非効率的ではない、最も明確で最も単純でクリーンな方法で記述してください。これは、最も高価なリソースを最大限に活用します。プログラムを作成するか、後でデバッグする(理解が必要)か。パフォーマンスが十分でない場合、測定しますボトルネックがどこにあるか、そしてそれらを軽減する方法を見てください。非常にまれな場合にのみ、その際に個々の(ソース)指示について心配する必要があります。パフォーマンスとは、最初の行で適切なアルゴリズムとデータ構造を選択し、注意深くプログラミングして、十分に高速なマシンを取得することです。優れたコンパイラーを使用すると、最新のコンパイラーが再構成するコードの種類を見ると驚くでしょう。パフォーマンスのためにコードを再構築することは、一種の最後の手段です。コードはより複雑になり(したがってバグが多く)、変更が難しくなるため、全体的にコストが高くなります。
一部のCPU(X86など)は、そのような分岐予測待ち時間を回避するために、プログラミングレベルに分岐予測を提供します。
一部のコンパイラーは、(GCCのように)これらを(C / C ++のような)より高いレベルのプログラミング言語への拡張として公開します。
LinuxカーネルのLike()/ Unlikely()マクロを参照してください-どのように機能しますか?彼らの利点は何ですか?。
私は一度、友人とこの議論をしました。彼は非常にナイーブなサークルアルゴリズムを使用していましたが、自分よりも高速であると主張しました(サークルの1/8しか計算しない種類)。最後に、ifステートメントはsqrtに置き換えられ、どういうわけかそれはより高速でした。おそらく、FPUにはsqrtが組み込まれているからでしょうか?
ALUの使用の点で最も高価ですか?比較対象の値を格納するためにCPUレジスタを使い果たし、ifステートメントが実行されるたびに値をフェッチして比較するのに時間がかかります。
したがって、その最適化は、ループが実行される前に1つの比較を行い、結果を変数として格納することです。
あなたの行方不明の言葉を解釈しようとしているだけです。