最新のCPUインナーループ間接最適化
-
28-09-2019 - |
質問
から http://www.boost.org/community/implementation_variations.html
「...クラスを仮想メンバーから非仮想的なメンバーに変更したり、間接レベルのレベルを削除したりするなどのコーディングの違いは、内部ループに深く深く測定可能な違いを生む可能性は低い。同じ数のクロックサイクルで競合するコードシーケンス!」
「内側のループでも」部分を理解しようとしています。具体的には、同じ数のクロックサイクル内で2つのコード(仮想vs非仮想レベルまたは追加レベルの間接レベル)を実行するためにCPUが実装するメカニズムは何ですか?私は命令のパイプラインとキャッシュについて知っていますが、非仮想コールと同じ数のクロックサイクル内で仮想呼び出しを実行することはどのように可能ですか?間接はどのように「失われていますか」ですか?
解決
キャッシュ(例: 分岐ターゲットキャッシング)、並列負荷ユニット(パイプラインの一部だけでなく、パイプラインを失速させない「ヒットミス」のようなもの)、および 秩序外の実行 変換するのに役立つ可能性があります load
-load
-branch
固定に近いものに branch
. 。パイプラインのデコードまたは分岐予測段階での命令折りたたみ/排除(これの適切な用語は何ですか?)も寄与する可能性があります。
ただし、これらはすべてさまざまなものに依存しています。さまざまな分岐ターゲットがいくつありますか(たとえば、トリガーする可能性のある異なる仮想過負荷の数)、ループするものの数(ブランチターゲットキャッシュは「温かい」ですか? Icache/dcacheについてはどうですか?)、仮想テーブルまたは間接テーブルはどのようにメモリにレイアウトされていますか(キャッシュに優しいですか、それぞれの新しいvtable負荷が古いvtableを追い出す可能性がありますか?)、キャッシュは繰り返し無効になっていますか?マルチコアpingpongなど...
(免責事項:私は間違いなくここでは専門家ではありません。私の知識の多くは、注文の埋め込みプロセッサを勉強することから来ているので、これのいくつかは外挿です。修正がある場合は、自由にコメントしてください!)
特定のプログラムの問題になるかどうかを判断する正しい方法は、もちろんプロファイルです。可能であれば、ハードウェアカウンターの助けを借りてそうしてください。パイプラインのさまざまな段階で何が起こっているかについて多くのことを伝えることができます。
編集:
Hans Passantが上記のコメントで指摘しているように 最新のCPUインナーループ間接最適化, 、これらの2つのことを同じ時間にかけるための鍵は、サイクルごとに複数の指示を効果的に「引退」する能力です。指示の除去はこれに役立ちますが スーパースカラーデザイン おそらくより重要です(Missの下でヒットは非常に小さく具体的な例です。完全に冗長な負荷ユニットはより良いものかもしれません)。
理想的な状況を取り、直接支店が1つの指示にすぎないと仮定しましょう。
branch dest
...そして間接的なブランチは3つです(たぶん2つで入手できるかもしれませんが、1つ以上です):
load vtable from this
load dest from vtable
branch dest
絶対に完璧な状況を仮定しましょう。 *これとVtable全体がL1キャッシュにあり、L1キャッシュは2つの負荷の命令コストごとに1サイクルの償却をサポートするのに十分な速さです。 (プロセッサが負荷を並べ替えて、ブランチの前に時間を完了するための以前の指示と混合されたと仮定することもできます。この例では問題ではありません。)また、ブランチターゲットキャッシュが熱く、パイプラインはないと仮定します。ブランチのフラッシュコスト、およびブランチ命令は単一のサイクル(償却)に帰着します。
理論的最小 したがって、最初の例の時間は1サイクルです(償却)。
2番目の例の理論的最小値は、命令の除去または冗長な機能ユニットまたはサイクルごとに複数の命令を廃止できるものは3サイクルです(3つの命令があります)!
サイクルごとに複数の命令を廃止できるスーパースカラーデザインのようなものに到達するまで、より多くの指示があるため、間接的な負荷は常に遅くなります。
これを手に入れると、両方の例の最小値は、他のすべてが理想的である場合、0〜1サイクルの間のものになります。おそらく、2番目の例には、最初の例よりもその理論的最小値に実際に到達するには、より理想的な状況が必要ですが、現在は可能です。
あなたが気にするいくつかのケースでは、おそらくどちらの例でもその最小限に到達することはないでしょう。ブランチターゲットキャッシュがコールドになるか、vtableがデータキャッシュに含まれないか、マシンが冗長機能ユニットを最大限に活用するための指示を並べ替えることができません。
...これはプロファイリングが登場する場所です。これは一般的に良い考えです。
君 できる そもそも仮想についてわずかな妄想を支持するだけです。見る Noel Llopisのデータ指向設計に関する記事, 、素晴らしい オブジェクト指向プログラミングスライドの落とし穴, 、 と マイク・アクトンの不機嫌そうなプレゼンテーション. 。多くのデータを処理している場合、CPUがすでに満足している可能性が高いパターンに突然移動しました。
仮想のような高レベルの言語機能は、通常、表現力と制御の間のトレードオフです。しかし、私は正直に言って、仮想が実際に何をしているかについてのあなたの認識を高めるだけで(時々分解ビューを読むことを恐れないでください、そして間違いなくあなたのCPUのアーキテクチャマニュアルを覗いてみると、あなたはそれを使用する傾向がありますそれが理にかなっていて、そうではないときではなく、プロファイラーは必要に応じて残りをカバーできます。
「仮想を使用しないでください」または「仮想使用を使用しないでください」というワンサイズフィットのステートメントは、測定可能な違いを生む可能性が低い」と私は不機嫌になります。現実は通常より複雑であり、あなたがそれをプロファイルや避けるのに十分な関心を持っている状況にあるか、またはおそらく教育コンテンツの可能性を除いて気にする価値がない他の95%にいるでしょう。
他のヒント
パイプラインが主な方法です。
命令を読み込み、デコードし、アクションを実行し、間接メモリ参照をロードするには、20クロックサイクルが必要になる場合があります。しかし、ピプレラインのために、プロセッサはパイプラインの異なる段階で他の19命令の一部を同時に実行できます。パイプラインにその命令を与えるのに実際にかかる時間にかかわらず、1クロックサイクルごとに1つの命令の全体的なスループットを提供します。
何が起こるか、プロセッサには、ブランチと間接的なジャンプの位置とターゲットを保持する特別なキャッシュがあると思います。間接的なジャンプが12345678ドルで遭遇し、最後に遭遇したときに12348765ドルを住所に遭遇した場合、プロセッサは、ブランチの住所を解決する前であっても、アドレス$ 12348765で指示の投機的実行を開始できます。多くの場合、関数の内部ループ内で、特定の間接ジャンプは、ループの期間中、常に同じアドレスにジャンプします。したがって、間接ジャンプキャッシュは、罰則の分岐を回避できます。
最新のCPUは、仮想関数のVtable実装で取得するなど、多くの間接的なジャンプを予測できる適応分岐予測手法を使用します。見る http://en.wikipedia.org/wiki/branch_prediction#prediction_of_indirect_jumps
CPUがすでにキャッシュにメモリアドレスを持っている場合、その場合、ロード命令の実行は些細なことです。