28

最新の CPU は大規模なパイプライン処理を備えています。つまり、実際に命令を実行するずっと前に、必要な命令とデータをロードしています。

パイプラインにロードされたデータが無効になる場合があり、パイプラインをクリアして新しいデータを再ロードする必要があります。パイプラインの再充填にかかる時間はかなり長く、パフォーマンスが低下する可能性があります。

C で関数ポインターを呼び出した場合、パイプライン内のポインターが関数ポインターであり、次の命令のためにそのポインターに従う必要があることを認識できるほど、パイプラインはスマートですか? または、関数ポインターを使用すると、パイプラインがクリアされてパフォーマンスが低下しますか?

私は C で作業していますが、多くの関数呼び出しが v テーブルを介して行われる C++ では、これがさらに重要になると思います。


編集

@JensGustedt は次のように書いています。

関数呼び出しのパフォーマンスを実際に向上させるには、呼び出す関数を非常に短くする必要があります。コードを測定してこれを確認した場合は、その呼び出しをインライン化できるように設計を再検討する必要があります。

残念ながら、それが私が陥った罠かもしれません。

パフォーマンス上の理由から、ターゲット関数を小さく高速に記述しました。

ただし、関数ポインタによって参照されるため、他の関数に簡単に置き換えることができます (ポインタが別の関数を参照するようにするだけです!)。関数ポインタ経由で参照するので、インライン化できないと思います。

したがって、インライン化されていない非常に短い関数があります。

4

4 に答える 4

13

一部のプロセッサでは、間接分岐は常にパイプラインの少なくとも一部をクリアします。これは、常に予測を誤るからです。これは、インオーダー プロセッサの場合に特に当てはまります。

たとえば、私たちが開発したプロセッサでいくつかのタイミングを実行し、インライン関数呼び出し、直接関数呼び出し、間接関数呼び出し (仮想関数または関数ポインター。このプラットフォームでは同じです) のオーバーヘッドを比較しました。

小さな関数本体を作成し、数百万回の呼び出しのタイトなループで測定して、呼び出しペナルティだけのコストを決定しました。「インライン」関数は、関数本体 (基本的には単一のロード操作) のコストのみを測定するコントロール グループでした。direct 関数は、正しく予測された分岐 (これは静的なターゲットであり、PPC の予測子は常に正しく予測できるため) と関数のプロローグのペナルティを測定しました。bctrl間接関数は、間接分岐のペナルティを測定しました。

6 億 1440 万回の関数呼び出し:

inline:   411.924 ms  (   2 cycles/call )
direct:  3406.297 ms  ( ~17 cycles/call )
virtual: 8080.708 ms  ( ~39 cycles/call )

ご覧のとおり、直接呼び出しは関数本体より 15 サイクル多く、仮想呼び出し (関数ポインター呼び出しとまったく同じ) は直接呼び出しより 22 サイクル多くコストがかかります。これは、たまたま、パイプラインの開始 (命令フェッチ) と分岐 ALU の終了の間にあるパイプライン ステージの数とほぼ同じです。したがって、このアーキテクチャでは、間接分岐 (別名、仮想呼び出し) により、22 のパイプライン ステージが100% の確率でクリアされます。

他のアーキテクチャは異なる場合があります。これらの決定は、実装が非常に異なるため、プロセッサが「何を予測する必要がある」かについての仮定ではなく、直接の経験的測定または CPU のパイプライン仕様から行う必要があります。この場合、分岐予測子は bctrl がリタイアするまでどこに行くかを知る方法がないため、パイプラインのクリアが発生します。せいぜい、最後の bctrl と同じターゲットを対象としていると推測できますが、この特定の CPU はその推測さえ試みません。

于 2012-06-04T23:44:30.043 に答える
9

関数ポインターを呼び出すことは、C++ で仮想メソッドを呼び出すことと根本的に違いはありません。さらに言えば、return と根本的に違います。プロセッサーは、先を見越して、ポインター経由の分岐が近づいていることを認識し、プリフェッチ パイプラインでポインターを安全かつ効果的に解決してそのパスをたどることができるかどうかを判断します。これは、通常の相対分岐をたどるよりも明らかに困難で費用がかかりますが、最近のプログラムでは間接分岐が非常に一般的であるため、ほとんどのプロセッサが試みます。

Oli が言ったように、パイプラインを「クリア」する必要があるのは、分岐がオフセットによるものか変数アドレスによるものかに関係なく、条件付き分岐で予測ミスがあった場合のみです。ただし、プロセッサには、分岐アドレスのタイプに応じて異なる方法で予測するポリシーがある場合があります。一般に、プロセッサは、不適切なアドレスの可能性があるため、条件付き分岐から離れた間接パスを積極的にたどる可能性は低くなります。

于 2012-05-25T16:38:12.987 に答える
3

関数ポインターを介した呼び出しは、必ずしもパイプラインのクリアを引き起こすわけではありませんが、シナリオによっては可能性があります。重要なのは、CPU が事前に分岐先を効果的に予測できるかどうかです。

最新の「大きな」アウトオブオーダー コアが間接呼び出しを処理する方法1は、おおよそ次のとおりです。

  • 間接分岐を数回実行すると、間接分岐予測子は、将来分岐が発生するアドレスを予測しようとします。
  • 最初の間接分岐予測子は非常に単純で、単一の固定位置のみを「予測」できました。
  • 最近のほとんどの CPU のものを含む後の予測子は、はるかに複雑であり、多くの場合、間接ジャンプの繰り返しパターンを適切に予測し、ジャンプ ターゲットを以前の条件分岐または間接分岐の方向と相関させることができます。
  • 予測が成功した場合、間接呼び出しには通常の直接呼び出しと同様のコストがかかります。このコストは、コードの残りの部分とはほとんど「一致していません」(つまり、依存関係チェーンに参加していません)。呼び出しが非常に密でない限り、コードの最終的な実行時間は短くなる可能性があります。
  • 一方、予測が失敗した場合は、分岐方向の予測ミスと同様に、完全な予測ミスが発生します。周囲のコードに依存するため、この予測ミスのコストに一定の数値を設定することはできませんが、通常、フロントエンドで約 20 サイクルのバブルが発生し、実行時の全体的なコストは多くの場合同様になります。

したがって、これらの基本があれば、特定のシナリオで何が起こるかについて、知識に基づいた推測を行うことができます。

  1. 関数ポインターは常に同じ関数を指し、ほとんどの場合、適切に予測され、通常の関数呼び出しとほぼ同じコストがかかります
  2. 複数のターゲット間をランダムに交互に切り替える関数ポインターは、ほとんどの場合、予測を誤ってしまいます。せいぜい、予測器が常に最も一般的なターゲットを予測することを期待できます。したがって、最悪の場合、ターゲットがターゲット間で一様にランダムに選択される場合N、予測の成功率は制限され1 / Nます (つまり、N が無限大になるとゼロになります)。 )。この点で、間接分岐は条件付き分岐よりも最悪の場合の動作が悪く、通常、最悪の場合の誤予測率は 50%です2
  3. 動作が中間の関数ポインタ (たとえば、ある程度予測可能 (たとえば、繰り返しパターンに従う)) の関数ポインタの予測率は、ハードウェアの詳細と予測子の精巧さに大きく依存します。最新の Intel チップには非常に優れた間接的な予測因子がありますが、詳細は公開されていません。従来の知恵では、条件分岐にも使用されるTAGE 予測子の間接的なバリアントを使用していると考えられています。

1予測子はまだ見たことのない間接的な呼び出しを予測できないため、関数が最初に (または数回) 遭遇したときに、単一のターゲットに対してさえ予測を誤るケースが含まれます。また、CPU 内の予測リソースのサイズには制限があるため、しばらく関数ポインタを使用していないと、最終的に予測リソースが他のブランチに使用され、次に呼び出すときに予測を誤ることになります。 .

2実際、最近最も頻繁に見られる方向を単純に予測する非常に単純な条件付き予測子は、完全にランダムな分岐方向で 50% の予測率を持つ必要があります。50% の結果よりも大幅に悪化するには、本質的に予測子をモデル化し、常にモデルの反対方向に分岐することを選択する敵対的アルゴリズムを設計する必要があります。

于 2018-05-27T22:39:52.537 に答える
1

関数ポインター呼び出しと「通常の」呼び出しの間に大きな違いはありませんが、余分なレベルの間接性があります。そのため、より大きなレイテンシが関係する可能性があります。宛先アドレスがまだキャッシュまたはレジスタにない場合、CPU はメイン メモリから取得されるまで待機する必要がある可能性があります。

答えは次のとおりです。はい、パイプラインは停止する可能性がありますが、これは通常の関数呼び出しと同じです。そしていつものように、分岐予測やアウトオブオーダー実行などのメカニズムは、ペナルティを最小限に抑えるのに役立ちます.

于 2012-05-25T15:46:32.887 に答える