C# での仮想呼び出しのコストは、相対的に言えば C++ ほど高くないことをどこかで読んだことを思い出すようです。これは本当ですか?もしそうなら - なぜですか?
9 に答える
AC# 仮想呼び出しは「this」が null であることをチェックする必要がありますが、C++ 仮想呼び出しはチェックしません。したがって、一般的に、C# の仮想呼び出しが高速になる理由はわかりません。特殊なケースでは、C# コンパイラ (または JIT コンパイラ)はC++ コンパイラよりも適切に仮想呼び出しをインライン化できる場合があります。これは、C# コンパイラがより適切な型情報にアクセスできるためです。C# JIT は、C++ コンパイラよりもランタイム メモリ レイアウトとプロセッサ モデルについて詳しく知っているため、小さなオフセットにのみ対処する高速な命令を使用できるため、C++ ではメソッド呼び出し命令が遅くなることがあります。
ただし、ここではせいぜい一握りのプロセッサ命令について話しているにすぎません。最新のスーパースカラー プロセッサでは、「メソッドの呼び出し」と同時に「ヌル チェック」命令が実行される可能性が非常に高く、したがって時間がかかりません。
また、呼び出しがループ内で行われる場合、すべてのプロセッサ命令がすでにレベル 1 キャッシュにある可能性が非常に高くなります。しかし、データがキャッシュである可能性は低く、最近のメイン メモリからデータ値を読み取るコストは、レベル 1 キャッシュから数百の命令を実行するのと同じです。したがって、実際のアプリケーションでは、仮想呼び出しのコストが非常に少ない場所で測定可能であることは不運です。
C# コードがさらにいくつかの命令を使用するという事実は、もちろん、キャッシュに収まるコードの量を減らします。これによる影響は予測できません。
(C++ クラスが複数の継承を使用する場合、"this" ポインターを修正する必要があるため、コストが高くなります。同様に、C# のインターフェイスは別のレベルのリダイレクトを追加します。)
JITコンパイル言語(CLRがこれを行うかどうかはわかりませんが、SunのJVMは行います)の場合、2つまたは3つの実装しかない仮想呼び出しを、型および直接またはインラインの一連のテストに変換するのが一般的な最適化です。呼び出します。
これの利点は、最新のパイプラインCPUが分岐予測と直接呼び出しのプリフェッチを使用できることですが、間接呼び出し(高水準言語では関数ポインターで表される)によってパイプラインストールが発生することがよくあります。
仮想呼び出しの実装が1つだけで、呼び出しの本体が十分に小さいという限定的なケースでは、仮想呼び出しは純粋なインラインコードに縮小されます。この手法は、JVMが発展した自己言語ランタイムで使用されました。
ほとんどのC++コンパイラは、この最適化を実行するために必要なプログラム全体の分析を実行しませんが、LLVMなどのプロジェクトは、このようなプログラム全体の最適化を検討しています。
この仮定は JIT コンパイラに基づいていると思います。つまり、C# は仮想呼び出しを実際に使用する少し前に単純なメソッド呼び出しに変換する可能性があります。
しかし、それは本質的に理論的なものであり、私はそれに賭けません!
C++ での仮想呼び出しのコストは、ポインター (vtbl) を介した関数呼び出しのコストです。私はC#がそれをより速く実行でき、実行時にオブジェクトタイプを決定できるとは思えません...
編集: Pete Kirkham が指摘したように、優れた JIT は C# 呼び出しをインライン化し、パイプラインの停止を回避できる可能性があります。ほとんどの C++ コンパイラでは (まだ) できないことです。一方、Ian Ringrose 氏は、キャッシュ使用量への影響について言及しました。それに加えて、JIT 自体が実行されており、(厳密に個人的には) 現実的なワークロードの下でターゲット マシンでプロファイリングを行って、一方が他方よりも高速であることが証明されない限り、私はあまり気にしません。せいぜいマイクロ最適化です。
C# は vtable をフラット化し、先祖の呼び出しをインライン化するため、継承階層をチェーンアップして何かを解決する必要はありません。