最適化オプション C++でのAIアプリケーション:仮想関数のコストはどれくらいですか? 可能な最適化は何ですか?




コンパイラ 最適 化 (12)

私がC ++で書いているAIアプリケーションでは、

  1. あまり数値計算がない
  2. 実行時の多型が必要な構造がたくさんあります
  3. 非常に頻繁に、いくつかの多形構造が計算中に相互作用します

このような状況では、最適化手法はありますか? 今ではアプリケーションを最適化する気にはなりませんが、このプロジェクトのJava over C ++を選択する方法の1つは、オブジェクト指向ではないメソッド(テンプレート、プロシージャ、オーバーロード)を最適化して使用できるようにすることです。

特に、仮想関数に関連する最適化テクニックは何ですか? 仮想関数は、メモリ内の仮想テーブルを通じて実装されます。 これらの仮想テーブルをL2キャッシュにプリフェッチする方法がいくつかあります(メモリ/ L2キャッシュからのフェッチのコストが増えています)。

これとは別に、C ++のデータ局所性テクニックの良いリファレンスはありますか? これらの手法は、計算に必要なL2キャッシュへのデータフェッチの待機時間を短縮します。

更新 :以下の関連フォーラムも参照してください: インタフェースのパフォーマンスペナルティ 、ベースクラスのいくつかのレベル


Answer #1

私が考えることができる唯一の最適化は、JavaのJITコンパイラです。 私が正しく理解すれば、コードが実行されるときに呼び出しを監視し、ほとんどの呼び出しが特定の実装のみに進むと、クラスが正しいときに条件付きの実装を実装に挿入します。 このように、ほとんどの場合、vtable検索はありません。 もちろん、まれに、別のクラスを渡すときにvtableが使用されます。

私は、この技術を使用するC ++コンパイラ/ランタイムを認識していません。



Answer #3

仮想関数は非常に効率的です。 32ビットポインタを仮定すると、メモリレイアウトはおよそ次のようになります。

classptr -> [vtable:4][classdata:x]
vtable -> [first:4][second:4][third:4][fourth:4][...]
first -> [code:x]
second -> [code:x]
...

classptrは通常、ヒープ上、時にはスタック上のメモリを指し、そのクラスのvtableへの4バイトのポインタで始まります。 しかし、覚えておくべき重要なことは、vtable自体がメモリに割り当てられていないことです。 これは静的リソースで、同じクラスタイプのすべてのオブジェクトは、vtable配列のまったく同じメモリ位置を指します。 異なるインスタンスを呼び出しても、異なるメモリ位置がL2キャッシュに引き込まれることはありません。

msdnのこのは、仮想関数func1、func2、およびfunc3を持つクラスAのvtableを示しています。 12バイトを超えるものはありません。 コンパイルされたライブラリには、異なるクラスのvtableも物理的に隣接している可能性があります(これは、あなたが特に関心があるかどうかを確認したいと思うでしょう)。

CONST SEGMENT
??[email protected]@[email protected]
   DD  FLAT:?[email protected]@@UAEXXZ
   DD  FLAT:?[email protected]@@UAEXXZ
   DD  FLAT:?[email protected]@@UAEXXZ
CONST ENDS

その他のパフォーマンス上の問題は、vtable関数を呼び出す際の命令オーバーヘッドになります。 これはまた非常に効率的です。 非仮想関数の呼び出しとほぼ同じです。 再びmsdn例から

; A* pa;
; pa->func3();
mov eax, DWORD PTR _pa$[ebp]
mov edx, DWORD PTR [eax]
mov ecx, DWORD PTR _pa$[ebp]
call  DWORD PTR [edx+8]

この例では、スタックフレームのベースポインタebpはゼロオフセットで変数A* paを持ちます。 レジスタeaxには位置[ebp]の値がロードされるため、A *があり、edxには位置[eax]の値がロードされているため、クラスAのvtableを持ちます。 ecxは "this"を表すので、ecxには[ebp]がロードされ、現在はA *が保持され、最後にvtableの3番目の関数アドレスである[edx + 8]の値が呼び出されます。

この関数呼び出しが仮想でない場合は、mov eaxとmov edxは必要ありませんが、パフォーマンスの違いはそれほど大きくありません。


Answer #4

仮想関数は、ルックアップと間接関数呼び出しである傾向があります。 いくつかのプラットフォームでは、これは高速です。 他のもの、例えば、コンソールで使用されているPPCアーキテクチャのように、これはあまり速くない。

通常、最適化はコールスタック内での変動をより高く表現するため、ホットスポット内で仮想関数を複数回呼び出す必要はありません。


Answer #5

ポリモフィクションは、仮想関数を使用して実行時に実装することも、コンパイル時にテンプレートを使用して実装することもできます。 仮想関数をテンプレートで置き換えることができます。 詳細については、この記事を参照してください - http://www.codeproject.com/KB/cpp/SimulationofVirtualFunc.aspx


Answer #6

実際にプロファイリングして、どこで最適化が必要なのかを見つけましたか?

実際にボトルネックになっていることがわかったときに仮想関数呼び出しを実際に最適化する作業を行います。


Answer #7

他の答えですでに述べたように、仮想関数呼び出しの実際のオーバーヘッドはかなり小さいです。 これは、1秒間に数百万回と呼ばれるタイトなループで違いを生むかもしれませんが、めったに大きな問題ではありません。

しかし、コンパイラが最適化するのが難しいという点で、まだ大きな影響を与える可能性があります。 コンパイル時に呼び出される関数がわからないため、関数呼び出しをインライン化できません。 また、グローバルな最適化が難しくなります。 そして、どれだけのパフォーマンスがあなたにかかるのですか? 場合によります。 通常、心配することはありませんが、重要なパフォーマンスヒットを意味するケースがあります。

もちろん、CPUのアーキテクチャにも依存します。 いくつかはかなり高価になることがあります。

しかし、どのような種類のランタイム・ポリモーフィズムでも同じオーバヘッドが発生することに注意してください。 switch文やそれに類するものを使って同じ機能を実装すると、可能な関数の数を選択するほうが安価ではないかもしれません。

これを最適化する唯一の信頼できる方法は、コンパイル時に作業の一部を移動できる場合です。 静的多型としてその一部を実装することができれば、いくつかの高速化が可能です。

しかしまず、問題があることを確認してください。 コードは実際に受け入れられるには遅すぎますか? 次に、プロファイラを使用して速度を遅くする原因を見つけます。 そして第三に、それを修正してください。



Answer #9

現代的で先見的なマルチディスパッチCPUでは、仮想関数のオーバーヘッドはゼロになる可能性があります。 ナダ。 ジップ。


Answer #10

最近のCPUSのコストは、今日の通常の機能とほぼ同じですが、インライン化することはできません。 関数を何百万回も呼び出すと、影響が大きくなることがあります(たとえば、同じ関数を何回も呼び出してみましょう。たとえば、一度は一度も使用しないでインラインで呼び出すと、関数自体が単純な場合は2倍遅くなることがあります。理論的なケースではありません。多くの数値計算では非常に一般的です)。


Answer #11

動的多型への解決策は静的多型です。型がコンパイルタイプで分かっている場合に使用できます:CRTP(Curiously Recurring template pattern)。

http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

Wikipediaについての説明は十分明確であり、 実際には仮想メソッドコールがパフォーマンスのボトルネックの原因であると判断した場合に役立つかもしれません。


Answer #12

私は実際に言うすべての答えを強化しています:

  • 実際にそれが問題であることがわからない場合は、それを修正することについての心配はおそらく間違っています。

あなたが知りたいことは次のとおりです:

  • 実行時間の何分の1(実際に実行されているか)がメソッドを呼び出すプロセスに費やされます。特に、どのメソッドが(この方法で)最もコストがかかりますか。

一部のプロファイラーは、間接的にこの情報を提供することができます。 ステートメントレベルで要約する必要がありますが、メソッド自体に費やされる時間は除きます。

私が気に入っているのは、デバッガの下で何度もそれを一時停止することです。

仮想関数呼出しの処理に費やされた時間が20%というように重要である場合、平均で5つのサンプルのうちの1つが、コールスタックの最下部に、逆アセンブリウィンドウに、仮想に従うための指示関数ポインタ。

あなたが実際にそれを見ないなら、それは問題ではありません。

このプロセスでは、コールスタックの上位にある他のものが実際には必要ないことがわかり、多くの時間を節約できます。





optimization