C ++はJITを使用したJVMまたはCLRよりも高速であるという主張を裏付けるものは何ですか?[閉まっている]


119

多くの質問で気づいたSEの繰り返しのテーマは、C ++がJavaのような高レベル言語よりも高速で効率的であるという継続的な議論です。反論は、最近のJVMまたはCLRは、増加するタスクのJITなどのおかげで同じように効率的であり、C ++ は、あなたが何をしていて、なぜ特定の方法で物事をしているのかを知っている場合にのみ効率的であるということですパフォーマンスの向上に値します。それは明らかであり、完全に理にかなっています。

JVMやCLRよりもC ++で特定のタスクが高速である理由方法についての基本的な説明(そのようなことがある場合)を知りたいのですが?JVMまたはCLRが実行時にJITコンパイルの処理オーバーヘッドをまだ持っているのに対して、C ++がマシンコードにコンパイルされているからでしょうか?

トピックを調査しようとすると、C ++が高性能コンピューティングにどのように利用できるかを正確に理解することに関する詳細な情報なしで、上で概説したのと同じ議論だけを見つけます。


パフォーマンスは、プログラムの複雑さにも依存します。
パンドゥ

23
「C ++は、何をしているのか、そして特定の方法でパフォーマンスを向上させるメリットがあるのか​​を知っている場合にのみ、C ++の効率が向上します」と付け加えます。それは知識の問題であるだけでなく、開発者の時間の問題だとも言っています。最適化を最大化することは常に効率的ではありません。これが、JavaやPythonなどの高レベル言語が存在する理由です(他の理由もあります)-高度に調整された最適化を犠牲にして、プログラマが特定のタスクを達成するためにプログラミングに費やす時間を減らすため。
ジョエルコーネット

4
@ジョエル・コーネット:私は完全に同意します。私は間違いなくC ++よりもJavaの方が生産的であり、非常に高速なコードを記述する必要がある場合にのみC ++を検討します。一方で、C ++コードの作成が不十分で、実際に遅いことがわかりました。C++は、未熟なプログラマーにとってはあまり有用ではありません。
ジョルジオ

3
JITで作成できるコンパイル出力はすべてC ++で作成できますが、C ++で作成できるコードは必ずしもJITで作成できるとは限りません。そのため、C ++の機能とパフォーマンス特性は、高水準言語の機能とパフォーマンス特性のスーパーセットです。QED
tylerl

1
@Doval技術的には正しいのですが、原則として、一方でプログラムのパフォーマンスに影響を与える可能性のあるランタイム要因をカウントできます。通常、3本以上の指を使用しません。最悪の場合、複数のバイナリを出荷します。ただし、潜在的な高速化は無視できるため、それを行う必要さえないことがわかります。
タイラー14年

回答:


200

それはすべてメモリに関するものです(JITではありません)。JITの「Cに対する利点」のほとんどは、インライン化による仮想または非仮想呼び出しの最適化に限定されています。これは、CPU BTBがすでに懸命に取り組んでいることです。

現代のマシンでは、RAMへのアクセスは(CPUが行うものと比較して)非常に遅いため、可能な限りキャッシュを使用するアプリケーション(少ないメモリを使用する方が簡単)は、それより最大で100倍高速になりますしないでください。また、JavaがC ++よりも多くのメモリを使用し、キャッシュを完全に活用するアプリケーションの作成を困難にする多くの方法があります。

  • 各オブジェクトには少なくとも8バイトのメモリオーバーヘッドがあり、多くの場所(つまり、標準コレクション)でプリミティブの代わりにオブジェクトの使用が必要または優先されます。
  • 文字は2つのオブジェクトで構成され、オーバーヘッドは38バイトです
  • UTF-16は内部で使用されます。つまり、各ASCII文字には1バイトではなく2バイトが必要です(Oracle JVMは最近、純粋なASCII文字列に対してこれを回避するための最適化を導入しました)。
  • 集約参照型(構造体など)はありません。また、集約参照型の配列はありません。JavaオブジェクトまたはJavaオブジェクトの配列は、C構造体および配列と比較して、L1 / L2キャッシュの局所性が非常に低くなっています。
  • Javaジェネリックは型消去を使用しますが、これは型インスタンス化に比べてキャッシュの局所性が劣っています。
  • オブジェクトの割り当ては不透明で、オブジェクトごとに個別に行う必要があるため、アプリケーションがデータをキャッシュフレンドリーな方法で意図的にレイアウトし、それを構造化データとして扱うことはできません。

その他のメモリ関連の要素:キャッシュに関連しない要素:

  • スタック割り当てがないため、操作するすべての非プリミティブデータはヒープ上にあり、ガベージコレクションを通過する必要があります(最近のJITの中には、場合によっては背後でスタック割り当てを行うものもあります)。
  • 集約参照タイプがないため、集約参照タイプのスタック受け渡しはありません。(ベクター引数の効率的な受け渡しを考えてください)
  • ガベージコレクションはL1 / L2キャッシュのコンテンツを傷つけ、GCの世界停止は対話性を傷つけます。
  • データ型間の変換には常にコピーが必要です。ソケットから取得した大量のバイトへのポインターを取得して、それらをフロートとして解釈することはできません。

これらのいくつかはトレードオフです(手動のメモリ管理を行わなくてもほとんどの人にとって多くのパフォーマンスを放棄する価値あります)、いくつかはおそらくJavaをシンプルに保とうとした結果であり、いくつかは設計ミスです(おそらく後知恵の場合のみ) 、つまり、UTF-16はJavaが作成されたときの固定長エンコードであったため、選択する決定がより理解しやすくなりました)。

これらのトレードオフの多くは、Java / JVMの場合とC#/ CILの場合とでは大きく異なることに注意してください。.NET CILには、参照型の構造体、スタックの割り当て/受け渡し、構造体のパック配列、および型インスタンス化ジェネリックがあります。


37
+1-全体として、これは良い答えです。ただし、「スタックの割り当てがない」箇条書きが完全に正確かどうかはわかりません。Java JITは、可能な場合にスタック割り当てを可能にするために、多くの場合、エスケープ分析を行います。おそらく、Java言語では、オブジェクトがスタック割り当てとヒープ割り当てのどちらであるかをプログラマーが判断できないことがあります。さらに、世代別ガベージコレクター(現代のすべてのJVMが使用する)が使用されている場合、「ヒープ割り当て」とは、C ++環境とはまったく異なる(パフォーマンス特性がまったく異なる)ことを意味します。
ダニエル・プライデン

5
他にも2つあると思いますが、私はたいていずっと高いレベルで仕事をしているので、間違っているかどうか教えてください。実際にメモリで何が起こっているのか、マシンコードが実際にどのように機能するのかについて、より一般的な認識を開発せずにC ++を書くことはできません。また、VMまたはインタープリター言語では、コアライブラリの作成者が過度に特定のシナリオに最適化した可能性があることに依存しているのに対して、物事の動作をよりきめ細かく制御できます。
エリックReppen

18
+1。もう1つ追加します(ただし、新しい回答を送信するつもりはありません)。Javaの配列インデックス付けには、常に境界チェックが含まれます。CおよびC ++では、これは当てはまりません。
リウォーク

7
Javaのヒープ割り当ては、C ++を使用した素朴なバージョン(内部プーリングなど)よりも大幅に高速ですが、C ++でのメモリ割り当ては、何をしているのかを知っていれば大幅に改善されることに注意してください。
ブレンダンロング

10
@BrendanLong、true ..ただし、メモリがきれいな場合のみ-しばらくアプリを実行すると、メモリを解放し、ファイナライザを実行する必要があるため、GCが劇的に遅くなるため、メモリ割り当てが遅くなります。コンパクト。そのトレードオフはベンチマークに役立ちますが、(IMHO)全体的にはアプリの速度が低下します。
gbjbaanb

67

それは、C ++がアセンブリ/マシンコードにコンパイルされているのに、Java / C#には実行時のJITコンパイルの処理オーバーヘッドが残っているからでしょうか?

部分的に、一般的には、絶対に素晴らしい最先端のJITコンパイラを仮定すると、適切なC ++コードは、依然として 2つの主な理由のためにJavaコードよりも優れて実行する傾向にあります。

1)C ++のテンプレートは、両方のコード書くための優れた機能を提供し、一般的な効率的。テンプレートは、C ++プログラマーにゼロのランタイムオーバーヘッドを持つ非常に便利な抽象化を提供します。(テンプレートは基本的にコンパイル時のダックタイピングです。)これとは対照的に、Javaジェネリックで得られるものは基本的に仮想関数です。仮想関数には常にランタイムのオーバーヘッドがあり、通常はインライン化できません。

一般に、Java、C#、さらにはCを含むほとんどの言語では、効率と汎用性/抽象化のどちらかを選択できます。C ++テンプレートは両方を提供します(コンパイル時間が長くなります)。

2)C ++標準では、コンパイルされたC ++プログラムのバイナリレイアウトについてあまり言及していないという事実により、C ++コンパイラはJavaコンパイラよりもはるかに余裕があり、より良い最適化が可能になります(デバッグが困難になる場合があります)。 )実際、Java言語仕様の性質上、特定の領域でパフォーマンスが低下します。たとえば、Javaでオブジェクトの連続した配列を持つことはできません。連続したオブジェクトポインターの配列しか持てません。(参照)。つまり、Javaで配列を反復処理すると、常に間接化のコストが発生します。ただし、C ++の値のセマンティクスは、連続した配列を有効にします。もう1つの違いは、C ++ではオブジェクトをスタックに割り当てることができますが、Javaではできません。つまり、実際には、ほとんどのC ++プログラムはスタックにオブジェクトを割り当てる傾向があるため、多くの場合、割り当てコストはゼロに近くなります。

C ++がJavaに遅れをとる可能性のある領域の1つは、多くの小さなオブジェクトをヒープに割り当てる必要がある状況です。この場合、Java GCはバルク割り当て解除を可能にするため、Javaのガベージコレクションシステムはおそらく標準newおよびdeleteC ++ よりもパフォーマンスが向上します。ただし、C ++プログラマーはメモリプールまたはスラブアロケーターを使用してこれを補うことができますが、Javaプログラマーは、Javaランタイムが最適化されていないメモリ割り当てパターンに直面した場合に頼ることはできません。

また、このトピックの詳細については、この優れた回答を参照してください。


6
良い答えですが、1つのマイナーポイント:「C ++テンプレートは両方を提供します(コンパイル時間が長くなります)。」プログラムサイズが大きくなるという犠牲を払います。常に問題になるとは限りませんが、モバイルデバイス向けに開発する場合は、間違いなく問題になる可能性があります。
レオ

9
@luiscubal:いいえ、この点で、C#ジェネリックは非常にJavaに似ています(どのタイプが渡されても同じ「ジェネリック」コードパスが使用されるため)。C++テンプレートの秘trickは、コードが1回インスタンス化されることです。適用されるすべてのタイプ。そうstd::vector<int>設計されたダイナミックアレイであるだけ int型のためには、コンパイラは、それに応じて最適化することができます。AC#List<int>はまだa Listです。
-jalf

12
@jalf C#List<int>はを使用しますがint[]Object[]Javaとは異なります。stackoverflow.com/questions/116988/を

5
@luiscubal:あなたの用語は不明です。JITは、私が「コンパイル時」であると考えているものには作用しません。もちろん、賢明で攻撃的なJITコンパイラーが十分に与えられていれば、それができることに制限はありません。ただし、C ++ ではこの動作が必要です。さらに、C ++テンプレートを使用すると、プログラマは明示的な特殊化を指定して、必要に応じて明示的な最適化を追加できます。C#にはこれに相当するものはありません。例えば、C ++で、Iは、定義することができvector<N>、特定の場合のために、どこにvector<4>手コード化されたSIMD実装は、使用されるべきである
jalf

5
@Leo:15年前のテンプレートによるコードの膨張は問題でした。重いテンプレート化とインライン化に加えて、コンパイラーが採用した機能(同一のインスタンスを折り畳むなど)により、最近のテンプレートでは多くのコードが小さくなります。
sbi

46

他の回答(これまでに6つ)が言及するのを忘れていたようですが、これに答えるために私が非常に重要だと思うものは、C ++の非常に基本的な設計哲学の1つです。

使用しないものに対しては支払いません。

C ++を大きく形作った他のいくつかの重要な基本設計原則(特定のパラダイムに強制されるべきではない)がありますが、最も重要なものの中で、使用しないものはすぐに支払われません


Stroustrupは、著書The Design and Evolution of C ++(通常[D&E]と呼ばれます)で、そもそもC ++を思いついた理由を説明しています。私自身の言葉で:彼の博士論文(ネットワークシミュレーション、IIRCに関連するもの)のために、彼はSIMULAにシステムを実装しました。しかし、結果のプログラムは非常に遅く実行され、学位を取得するために、Cの前身であるBCPLの内容を書き直しました。結果、博士号を取得することができました。

その後、彼は、現実世界の問題をできるだけ直接コードに変換できるようにするだけでなく、コードを非常に効率的にすることのできる言語を求めていました。
それを追求して、彼は後にC ++になるものを作成しました。


したがって、上記の目標は、基本的な基本設計原則の1つにすぎず、C ++の存在理由に非常に近いものです。そして、それは言語のほぼどこでも見つけることができます:関数はvirtualあなたが望むときだけです(仮想関数の呼び出しにはわずかなオーバーヘッドがあるため)PODは明示的にこれを要求したときにのみ自動的に初期化され、例外は実際にあなたがスタックフレームのセットアップ/クリーンアップを非常に安価にできるようにすることが明示的な設計目標だったのに対し、それを感じたときにGCが実行されないなど

C ++は、明示的に(「いいえ、私はない、と今コンパイラができますあなたのパフォーマンスと引き換えに、いくつかの便利さを(「私はここに、このメソッドは仮想にする必要がありますか?」)を得ていないことを選んだinlineこととのうちの一体を最適化します驚くべきことではありませんが、これにより実際に、より便利な言語と比較してパフォーマンスが向上しました。


4
使用しないものに対しては支払いません。=>そして、彼らはRTTIを追加しました:(
マチューM.

11
@Matthieu:私はあなたの感情を理解していますが、それでもパフォーマンスに関して慎重に追加されていることに気づかずにはいられません。RTTIは、仮想テーブルを使用して実装できるように指定されているため、使用しない場合のオーバーヘッドはほとんど追加されません。ポリモーフィズムを使用しない場合、費用は一切かかりません。何か不足していますか?
-sbi

9
@Matthieu:もちろん、理由があります。しかし、この理由は合理的ですか?私が見ることができることから、「RTTIのコスト」は、使用されない場合、すべてのポリモーフィッククラスの仮想テーブルの追加ポインタであり、どこかに静的に割り当てられたRTTIオブジェクトを指します。私のトースターでチップをプログラムしたくない限り、これはどのように関連するでしょうか?
-sbi

4
@Aaronaught:それに何を返信するか迷っています。Stroustrupらがパフォーマンスを可能にする方法で機能を追加した根本的な哲学を指摘しているので、これらの方法と機能を個別にリストするのではなく、本当に私の答えを却下しましたか?
sbi

9
@Aaronaught:あなたは私の同情を持っています。
sbi

29

そのトピックに関するGoogleの研究論文を知っていますか?

結論から:

パフォーマンスに関しては、C ++が大きな差で勝っていることがわかります。ただし、最も広範なチューニング作業も必要であり、その多くは平均的なプログラマーには利用できない高度なレベルで行われました。

「実世界のC ++コンパイラは、経験的手段によりJavaコンパイラよりも高速なコードを生成するため」という意味で、これは少なくとも部分的な説明です。


4
メモリとキャッシュの使用量の違いに加えて、最も重要なものの1つは、実行される最適化の量です。GCC / LLVM(およびおそらくVisual C ++ / ICC)がJava HotSpotコンパイラーに対して行う最適化の数を比較してください。特にループに関して、冗長な分岐とレジスター割り当てを排除します。JITコンパイラーは、通常、これらの積極的な最適化のための時間を持っていません。利用可能な実行時情報を使用して、それらをより適切に実装できるとさえ考えていました。
グラチアンラップ

2
@GratianLup:LTOについては(まだ)本当かと思います。

2
@GratianLup:のは、C ++ ...のプロファイルに基づく最適化を忘れないようにしましょう
デュプリケータ

23

これはあなたの質問の複製ではありませんが、受け入れられた答えはあなたの質問の大部分に答えます: Javaの最新のレビュー

総括する:

基本的に、Javaのセマンティクスは、JavaがC ++よりも遅い言語であることを示しています。

したがって、C ++を比較する他の言語に応じて、同じ答えが得られる場合と得られない場合があります。

C ++には次のものがあります。

  • スマートなインライン化を行う能力、
  • 強い局所性を持つ一般的なコード生成(テンプレート)
  • できるだけ小さくコンパクトなデータ
  • インダイレクションを回避する機会
  • 予測可能なメモリ動作
  • 高レベルの抽象化(テンプレート)を使用するためにのみ可能なコンパイラ最適化

これらは言語定義の機能または副作用であり、メモリおよび速度に関して理論的には次の言語よりも効率的です。

  • インダイレクションを大量に使用する(「すべてがマネージリファレンス/ポインタ」言語):インダイレクションは、CPUが必要なデータを取得するためにメモリにジャンプする必要があることを意味し、CPUキャッシュエラーが増加します。つまり、処理速度が低下します。 C ++として小さなデータを保持できる場合でも、
  • メンバーが間接的にアクセスされる大きなサイズのオブジェクトを生成します:これはデフォルトで参照を持っていることの結果であり、メンバーはポインターであるため、メンバーを取得すると、親オブジェクトのコアに近いデータを取得できず、再びキャッシュミスが発生します。
  • ガーバージコレクターを使用します。パフォーマンスの予測可能性を(設計上)不可能にするだけです。

コンパイラーのC ++アグレッシブなインライン化により、多くの間接化が削減または排除されます。コンパクトなデータの小さなセットを生成する能力により、これらのデータをまとめてパックするのではなく、メモリ全体に分散しない場合、キャッシュフレンドリーになります(両方とも可能です。C++で選択できます)。RAIIは、C ++メモリの動作を予測可能にし、高速または高速を必要とするリアルタイムまたはセミリアルタイムシミュレーションの場合の多くの問題を排除します。局所性の問題は、一般にこれによって要約できます。プログラム/データが小さいほど、実行は速くなります。C ++は、データが目的の場所(プール、配列など)にあり、コンパクトであることを確認するためのさまざまな方法を提供します。

明らかに、同じことができる他の言語もありますが、C ++ほど多くの抽象化ツールを提供しないため、あまり人気がありません。したがって、多くの場合、あまり有用ではありません。


7

主にメモリに関するもので(Michael Borgwardtが述べたように)、JITの非効率性が少し加えられています。

言及されていないことの1つはキャッシュです。キャッシュを完全に使用するには、データを連続して(つまりすべて一緒に)レイアウトする必要があります。現在、GCシステムでは、メモリはGCヒープに割り当てられますが、これは高速ですが、メモリが使用されると、GCは定期的に起動し、使用されなくなったブロックを削除し、残りをまとめて圧縮します。これらの使用済みブロックを一緒に移動することの明らかな遅さは別として、これは、使用しているデータが結合されていない可能性があることを意味します。1000個の要素の配列がある場合、それらを一度に割り当てない限り(そして、ヒープの終わりに作成される新しい要素を削除および作成するのではなく、コンテンツを更新しない限り)、これらはヒープ全体に散在します。したがって、すべてをCPUキャッシュに読み込むには、いくつかのメモリヒットが必要です。AC / C ++アプリは、おそらくこれらの要素にメモリを割り当ててから、データでブロックを更新します。(OK、リストのようなデータ構造があり、GCメモリ割り当てのように振る舞いますが、人々はこれらがベクトルより遅いことを知っています)。

StringBuilderオブジェクトをString ...に置き換えるだけで、動作を確認できます。Stringbuildersは、メモリを事前に割り当ててメモリを埋めることで機能し、java / .NETシステムのパフォーマンストリックとして知られています。

「古いものを削除して新しいコピーを割り当てる」というパラダイムがJava / C#で非常に頻繁に使用されることを忘れないでください。これは、メモリ割り当てがGCにより非常に高速であり、分散メモリモデルがどこでも使用されると言われているからです(もちろん、文字列ビルダーを除く)すべてのライブラリはメモリを浪費し、多くのメモリを使用する傾向がありますが、いずれも連続性のメリットを得られません。これについてGCの誇大宣伝を非難-彼らはあなたが記憶が無料だったと言った、笑。

GC自体は明らかに別のパフォーマンスヒットです。実行時には、ヒープをスイープするだけでなく、未使用のブロックをすべて解放する必要があり、ファイナライザーを実行する必要があります(ただし、次回はアプリを停止して)(まだパフォーマンスヒットであるかどうかはわかりませんが、読むドキュメントはすべて、本当に必要な場合はファイナライザーのみを使用すると言います)そして、それらのブロックを適切な位置に移動して、ヒープを圧縮し、ブロックの新しい場所への参照を更新します。ご覧のとおり、大変な作業です!

C ++メモリのパフォーマンスヒットはメモリ割り当てに影響します-新しいブロックが必要な場合は、ヒープを歩き回って、断片化されたヒープで十分な大きさの次の空きスペースを探す必要があります。これはGCほど高速ではありません。 「最後に別のブロックを割り当てる」だけですが、GCの圧縮が行うすべての作業ほど遅くはなく、複数の固定サイズのブロックヒープ(メモリプールとも呼ばれます)を使用して軽減できると思います。

さらに...セキュリティチェックを必要とするGACからのアセンブリの読み込み、プローブパス(sxstraceを有効にして、近づいているものを確認するなど)、およびjava / .netで非常に人気があると思われるその他の一般的なオーバーエンジニアリングなどC / C ++より。


2
あなたが書く多くのことは、現代の世代別のガベージコレクターには当てはまりません。
マイケルボルグワード

3
@MichaelBorgwardtなど?「GCは定期的に実行されます」と「ヒープを圧縮します」と言います。私の答えの残りは、アプリケーションのデータ構造がメモリを使用する方法に関するものです。
gbjbaanb 14

6

「C ++がアセンブリ/マシンコードにコンパイルされているのに、Java / C#には実行時のJITコンパイルの処理オーバーヘッドが残っているからですか?」基本的にはい!

ただし、Javaには、単にJITコンパイルよりも多くのオーバーヘッドがあります。たとえば、はるかに多くのチェックを実行します(これはArrayIndexOutOfBoundsExceptionsandのような方法ですNullPointerExceptions)。ガベージコレクターは別の重要なオーバーヘッドです。

ここにかなり詳細な比較があります


2

以下はネイティブコンパイルとJITコンパイルの違いを比較するだけであり、特定の言語またはフレームワークの詳細はカバーしないことに注意してください。これを超えて特定のプラットフォームを選択する正当な理由があるかもしれません。

ネイティブコードの方が速いと主張するとき、ネイティブコンパイルされたコードとJITコンパイルされたコードの典型的なユースケースについて話します。最初にコンパイラを待機します)。その場合、JITでコンパイルされたコードはネイティブコードに匹敵する、または打ち負かすことができると、誰もがまっすぐに主張することはできないと思います。

ある言語Xで書かれたプログラムがあり、ネイティブコンパイラでコンパイルでき、再びJITコンパイラでコンパイルできると仮定しましょう。各ワークフローには、(コード->中間表現->マシンコード->実行)として一般化できる同じステージが含まれます。2つの大きな違いは、ユーザーが見るステージとプログラマーが見るステージです。ネイティブコンパイルでは、プログラマは実行ステージ以外のすべてを見ることができますが、JITソリューションでは、実行に加えて、マシンコードへのコンパイルがユーザーに表示されます。

AがBより速いという主張、ユーザーに見えるように、プログラムの実行にかかる時間を指します。実行段階で両方のコードが同じように動作すると仮定する場合、マシンコードへのコンパイルの時間T(T> 0)も確認する必要があるため、JITワークフローはユーザーにとって遅いと仮定する必要があります。 、JITワークフローがネイティブワークフローと同じように実行される可能性がある場合、ユーザーはコードの実行時間を短縮し、実行+マシンコードへのコンパイルが実行ステージよりも短くなるようにする必要がありますネイティブのワークフローの。つまり、ネイティブコンパイルよりもJITコンパイルの方がコードを最適化する必要があります。

ただし、これはかなり実行不可能です。実行を高速化するために必要な最適化を実行するには、マシンコードステージへのコンパイルにより多くの時間を費やさなければならず、したがって、コンパイルに追加します。言い換えれば、JITベースのソリューションの「遅さ」は、単にJITコンパイルに時間を追加しただけでなく、そのコンパイルによって生成されたコードのパフォーマンスがネイティブソリューションよりも遅いためです。

例を使用します:レジスターの割り当て。メモリアクセスはレジスタアクセスよりも数千倍遅いため、可能な限りレジスタを使用し、できるだけ少ないメモリアクセスを行うことが理想ですが、レジスタの数は限られているため、必要なときに状態をメモリにスピルする必要がありますレジスタ。計算に200ミリ秒かかるレジスタ割り当てアルゴリズムを使用し、その結果、実行時間を2ミリ秒節約した場合、JITコンパイラの時間を最大限に活用できません。高度に最適化されたコードを生成できるChaitinのアルゴリズムのようなソリューションは不適切です。

JITコンパイラーの役割は、コンパイル時間と生成コードの品質の最適なバランスをとることです。ただし、ユーザーを待たせたくないので、高速コンパイル時間に大きな偏りがあります。JITの場合、実行されるコードのパフォーマンスは遅くなります。これは、ネイティブコンパイラがコードを最適化する際に時間に拘束されないため、最適なアルゴリズムを自由に使用できるためです。JITコンパイラの全体的なコンパイルと実行が、ネイティブにコンパイルされたコードの実行時間のみに勝る可能性は事実上0です。

ただし、VMは単にJITコンパイルに限定されているわけではありません。それらは、事前コンパイル技術、キャッシュ、ホットスワッピング、および適応最適化を採用しています。そのため、パフォーマンスはユーザーが見るものであるという主張を修正し、プログラムの実行にかかる時間に制限しましょう(AOTをコンパイルしたと仮定します)。実行中のコードをネイティブコンパイラと同等の効果的なものにすることができます(または、おそらくもっと良いのでしょうか?)。VMの大きな主張は、特定の機能が実行される頻度など、実行中のプロセスの情報にアクセスできるため、ネイティブコンパイラよりも高品質のコードを生成できる可能性があることです。VMは、ホットスワップを介して最も重要なコードに適応最適化を適用できます。

ただし、この引数には問題があります。プロファイルに基づく最適化などがVMに固有のものであると想定されていますが、これは正しくありません。プロファイリングを有効にしてアプリケーションをコンパイルし、情報を記録し、そのプロファイルでアプリケーションを再コンパイルすることにより、ネイティブコンパイルにも適用できます。また、コードのホットスワッピングはJITコンパイラーだけができることではなく、ネイティブコードに対してもできることを指摘する価値があります。ただし、これを行うためのJITベースのソリューションはより簡単に入手でき、開発者にとってはるかに簡単です。大きな問題は、VMがネイティブコンパイルでは得られない情報を提供して、コードのパフォーマンスを向上できるかどうかということです。

自分で見ることができません。プロセスはより複雑ですが、典​​型的なVMの技術のほとんどをネイティブコードにも適用できます。同様に、AOTコンパイルまたは適応最適化を使用するVMにネイティブコンパイラの最適化を適用できます。現実には、ネイティブに実行されるコードとVMで実行されるコードの違いは、私たちが信じさせられたほど大きくありません。最終的には同じ結果につながりますが、そこに到達するために異なるアプローチを取ります。VMは反復アプローチを使用して最適化されたコードを生成します。ネイティブコンパイラは最初からそれを想定しています(反復アプローチで改善できます)。

C ++プログラマーは、get-goからの最適化が必要だと主張するかもしれません。VMがそれらを行う方法を見つけ出すのを待っているべきではありません。VMの最適化の現在のレベルはネイティブコンパイラが提供できるものよりも劣るので、これはおそらく現在のテクノロジの有効なポイントです。


0

この記事は、C ++とC#の速度を比較しようとする一連のブログ投稿と、高性能なコードを取得するために両方の言語で克服しなければならない問題の要約です。要約は「あなたのライブラリは何よりも重要ですが、C ++を使用している場合はそれを克服できます。」または「現代の言語はより良いライブラリを持っているため、より少ない労力でより高速な結果を得ることができます」は、哲学的な傾向によって異なります。


0

ここでの本当の質問は「どちらが速いのか」ではないと思います。しかし、「どちらがより高いパフォーマンスを得るための最良の可能性を持っていますか?」これらの用語で見ると、C ++は明らかに勝ちます-ネイティブコードにコンパイルされている、JITがない、抽象化のレベルが低いなどです。

それは完全な話からはほど遠い。

C ++はコンパイルされるため、コンパイル時にコンパイラーの最適化を行う必要があります。また、あるマシンに適したコンパイラーの最適化は、別のマシンには完全に間違っている可能性があります。また、グローバルコンパイラの最適化によって、特定のアルゴリズムやコードパターンが他のアルゴリズムよりも優先される場合もあります。

一方、JIT化されたプログラムはJIT時に最適化されるため、プリコンパイルされたプログラムではできないいくつかのトリックを引き出し、実際に実行されているマシンと実際に実行されているコードに対して非常に具体的な最適化を行うことができます。JITの初期オーバーヘッドを超えると、場合によってはより高速になる可能性があります。

どちらの場合でも、アルゴリズムの賢明な実装と、愚かではないプログラマーの他のインスタンスは、はるかに重要な要因になる可能性があります-たとえば、C ++で完全に脳死した文字列コードを書くことは完全に可能です解釈されたスクリプト言語。


3
「あるマシンに適したコンパイラーの最適化は、別のマシンに完全に間違っているかもしれません。」まあ、それは実際には言語のせいではありません。本当にパフォーマンスが重要なコードは、実行するマシンごとに個別にコンパイルできます。これは、ソース(-march=native)からローカルにコンパイルする場合は簡単です。—「抽象化のレベルが低い」というのは本当ではありません。C ++は、Java(または実際、より高度なもの:関数型プログラミング?テンプレートメタプログラミング?)と同じ高レベルの抽象化を使用し、Javaよりも「クリーン」ではない抽象化を実装します。
leftaroundabout

「実際にパフォーマンスクリティカルなコードは、実行するマシンごとに個別にコンパイルできます。これは、ソースからローカルにコンパイルする場合は非常に簡単です」-これは、エンドユーザーもプログラマーであるという根本的な前提のため失敗します。
マキシマスミニマス

必ずしもエンドユーザーではなく、プログラムのインストールを担当する人だけです。デスクトップおよびモバイルデバイスでは、それは一般的、エンドユーザーが、これらは、ある確かにない最もパフォーマンスが重要なもののみのアプリケーションではありません。すべての優れたフリー/オープンソフトウェアプロジェクトのようにビルドスクリプトが適切に記述されていれば、ソースからプログラムをビルドするためにプログラマである必要はありません。
左辺

1
理論上はそうですが、JITは静的コンパイラーよりも多くのトリックを引き出すことができますが、実際には(少なくとも.NETの場合、Javaも知りません)、実際にはこれを行いません。私は最近、.NET JITコードの多くの逆アセンブリを行いました。ループからコードを巻き上げる、デッドコードの除去など、.NET JITが単にしないあらゆる種類の最適化があります。私はそれがほしい、ちょっと、マイクロソフト内部のWindowsチームは長年.NETを殺そうとしてきたので、私は私の息を保持していないよ
オリオンエドワーズ

-1

JITコンパイルは、実際にはパフォーマンスに悪影響を及ぼします。「完璧な」コンパイラーと「完璧な」JITコンパイラーを設計する場合、最初のオプションは常にパフォーマンスで勝ちます。

JavaとC#は両方とも中間言語に解釈され、実行時にネイティブコードにコンパイルされるため、パフォーマンスが低下します。

しかし、C#の違いはそれほど明白ではありません。MicrosoftCLRはCPUごとに異なるネイティブコードを生成するため、実行中のマシンでコードをより効率的にします。

PS C#は非常に効率的に記述されており、抽象化レイヤーはあまりありません。これはJavaには当てはまりませんが、Javaはそれほど効率的ではありません。そのため、この場合、CLRが優れているため、C#プログラムはC ++プログラムよりも優れたパフォーマンスを示すことがよくあります。.NetとCLRの詳細については、Jeffrey Richterの「C#via C#」をご覧ください。


8
JITが実際にパフォーマンスにマイナスの影響を与えた場合、確かに使用されませんか?
ザビエル

2
@Zavior-あなたの質問に対する良い答えは考えられませんが、JITがどのようにパフォーマンスのオーバーヘッドを追加できないのかわかりません-JITは、実行時に完了するべき追加のプロセスであり、プログラム自体の実行に費やされているのに対して、完全にコンパイルされた言語は「すぐに使用可能」です。
匿名

3
JITは、コンテキストに入れると、パフォーマンスにプラスの効果をもたらしますが、マイナスではなく、バイトコードをマシンコードにコンパイルしてから実行します。結果はキャッシュすることもできるため、解釈される同等のバイトコードよりも高速に実行できます。
ケーシークーボール

3
JIT(または、バイトコードアプローチ)は、パフォーマンスのためではなく、利便性のために使用されます。各プラットフォーム(またはそれらのすべてに対して準最適な共通のサブセット)のバイナリを事前に構築する代わりに、半分だけをコンパイルし、残りをJITコンパイラに任せます。「一度書いて、どこにでも展開する」がこの方法で行われる理由です。バイトコードインタープリターだけで便利ですが、JITは生のインタープリターよりも高速になります(プリコンパイルされたソリューションに勝るのに必ずしも十分ではありませんが、JITコンパイルに時間かかり、結果は常に成り立ちませんそれのための)。
-tdammers

4
@Tdammmers、実際にはパフォーマンスコンポーネントもあります。java.sun.com/products/hotspot/whitepaper.htmlを参照してください。最適化には、分岐予測とキャッシュヒットを改善する動的調整、動的インライン化、仮想化解除、境界チェックの無効化、ループ展開などが含まれます。主張は、多くの場合、これらはJITの費用を払う以上のものであるということです。
チャールズE.グラント
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.