web-dev-qa-db-ja.com

JavaがC ++よりも高速になる可能性があるのはなぜですか?

JavaはベンチマークでC++よりも優れている場合があります。もちろん、C++よりも優れている場合もあります。

次のリンクを参照してください。

しかし、これはどのようにして可能でしょうか?解釈されたバイトコードはコンパイルされた言語よりも速くなる可能性があるという私の心を揺さぶります。

誰かが説明できますか?ありがとう!

81
Deets McGeets

まず、ほとんどのJVMにはコンパイラが含まれているため、「解釈されたバイトコード」は実際には非常にまれです(少なくともベンチマークコードでは、コードが非常に頻繁に繰り返されるいくつかのささいなループよりも多いため、実際にはそれほど珍しくありません)。 )。

第2に、関連するベンチマークのかなりの数がかなり偏っているように見えます(意図または無能にかかわらず、私は実際には言えません)。たとえば、数年前、私はあなたが投稿したリンクの1つからリンクされているソースコードをいくつか見ました。それはこのようなコードを持っていました:

  init0 = (int*)calloc(max_x,sizeof(int));
  init1 = (int*)calloc(max_x,sizeof(int));
  init2 = (int*)calloc(max_x,sizeof(int));
  for (x=0; x<max_x; x++) {
    init2[x] = 0;
    init1[x] = 0;
    init0[x] = 0;
  }

callocはすでにゼロになっているメモリを提供するため、forループを使用して再度ゼロにすることは明らかに役に立たない。これに続いて(メモリが機能する場合)とにかくメモリを他のデータで満たす(そして、ゼロにされることに依存しない)ため、いずれにしてもすべてのゼロ化は完全に不要でした。上記のコードを単純なmallocに置き換えると(通常の人なら最初からそうでしたように)、C++バージョンの速度が(かなり広いマージンで)Javaバージョンに勝るほど向上しました、メモリが機能する場合)。

(別の例として)最後のリンクのブログエントリで使用されているmethcallベンチマークを検討してください。名前(およびどのように見えるか)にも関わらず、このC++バージョンはnotメソッド呼び出しのオーバーヘッドについて実際に多く測定しています。重要であることが判明したコードの一部は、Toggleクラスにあります。

class Toggle {
public:
    Toggle(bool start_state) : state(start_state) { }
    virtual ~Toggle() {  }
    bool value() {
        return(state);
    }
    virtual Toggle& activate() {
        state = !state;
        return(*this);
    }
    bool state;
};

重要な部分はstate = !state;です。状態をintではなくboolとしてエンコードするようにコードを変更するとどうなるかを考えます。

class Toggle {
    enum names{ bfalse = -1, btrue = 1};
    const static names values[2];
    int state;

public:
    Toggle(bool start_state) : state(values[start_state]) 
    { }
    virtual ~Toggle() {  }
    bool value() {  return state==btrue;    }

    virtual Toggle& activate() {
        state = -state;
        return(*this);
    }
};

この小さな変更により、全体の速度が約5:1のマージン向上します。ベンチマークはメソッド呼び出し時間を測定することを意図したものですが、実際には、ベンチマークはint間の変換時間でした。およびbool。オリジナルによって示された非効率性が不幸であることは確かに同意します-しかし、実際のコードで発生する可能性が非常に低いようであり、発生した場合/発生した場合に修正できる容易さを考えると、考えるのが難しいそれは多くの意味として。

誰かが関連するベンチマークを再実行することを決定した場合、生成するJavaバージョンにほぼ同じように簡単な変更があることも追加する必要があります(または少なくとも一度に生成されます-再実行していません) -最近のJVMでテストを実行して、テストがまだ行われていることを確認します)Javaバージョンもかなり大幅に改善されています。 Javaバージョンには、次のようなNthToggle :: activate()があります。

public Toggle activate() {
this.counter += 1;
if (this.counter >= this.count_max) {
    this.state = !this.state;
    this.counter = 0;
}
return(this);
}

this.stateを直接操作するのではなく、基本関数を呼び出すようにこれを変更すると、速度が大幅に向上します(ただし、変更されたC++バージョンに対応するには十分ではありません)。

したがって、最終的に解釈されるのは、解釈されたバイトコードとこれまでに見た(私が知っている)最悪のベンチマークのいくつかに関する誤った仮定です。どちらも意味のある結果をもたらしていません。

私自身の経験では、同等の経験を持つプログラマーが最適化に同等の注意を払っている場合、C++はJavaに勝る頻度が高くなります-(少なくともこれら2つの間で)、言語がプログラマーとデザイン。引用されているベンチマークは、彼らがベンチマークと主張している言語よりも、著者の(能力不足)/(不誠実)についてより多くを教えてくれます。

[編集:上記の1か所で暗示されているが、おそらく本来あるべきほど直接的には述べられていないため、引用している結果は、C++とJavaの実装を使用して、約5年前にこれをテストしたときに得られた結果です。それは当時の最新のものでした。現在の実装でテストを再実行していません。ただし、一見すると、コードが修正されていないことが示されているため、変更されたのはコンパイラーがコード内の問題をカバーする機能だけでした。]

ただし、Javaの例を無視すると、解釈されたコードがコンパイルされたコードよりも高速に実行される可能性があります(難しいが、やや珍しい)。

これが発生する通常の方法は、解釈されるコードがマシンコードよりもはるかにコンパクトであるか、コードキャッシュよりもデータキャッシュが大きいCPUで実行されていることです。

そのような場合、小さなインタプリタ(たとえば、Forth実装の内部インタプリタ)は完全にコードキャッシュに収まる場合があり、それが解釈するプログラムは完全にデータキャッシュに収まる場合があります。キャッシュは通常、メインメモリよりも少なくとも10倍高速であり、多くの場合それよりもはるかに高速です(100倍であることは特にまれではありません)。

したがって、キャッシュがメインメモリよりもN倍高速で、各バイトコードを実装するのに必要なマシンコード命令がNよりも少ない場合、バイトコードが優先されます(単純化していますが、一般的な考え方は明らかです)。

108
Jerry Coffin

手巻きC/C++ 無制限の時間で専門家によって行われますは、Javaと少なくとも同じかそれ以上の速度になります。結局のところ、Java自体はC/C++で記述されているので、十分なエンジニアリングの努力を惜しまなければ、もちろんすべてを行うことができますJava.

ただし、実際にはJavaは、以下の理由により、非常に高速に実行されることがよくあります。

  • JITコンパイル-Javaクラスはバイトコードとして格納されますが、これは(通常)プログラムの起動時にJITコンパイラによってネイティブコードにコンパイルされます。コンパイルされると、純粋なネイティブコードです-理論的には、プログラムが十分に長く実行された後(つまり、すべてのJITコンパイルが実行された後)、コンパイルされたC/C++と同様に実行することが期待できます。
  • ガベージコレクション in Javaは非常に高速で効率的です-Hotspot GCはおそらく世界で最高のオールラウンドGC実装です。これは多くの人年の結果ですSunや他の企業による専門家の努力の結果です。C/ C++で自分自身をロールバックする複雑なメモリ管理システムのほとんどは悪化します。もちろん、C/C++でかなり高速/軽量の基本的なメモリ管理スキームを記述できますが、それらはフルGCシステムとほぼ同じくらい用途が広く、最新のシステムのほとんどは複雑なメモリ管理を必要とするため、Javaは実際の状況に大きな利点があります。
  • より優れたプラットフォームターゲット-アプリケーションの起動(JITコンパイルなど)へのコンパイルを遅らせることにより、Javaコンパイラは知っているという事実を利用できますexactそれが実行されているプロセッサ。これにより、「最低公分母」プロセッサ命令セットを対象とする必要がある事前コンパイルされたC/C++コードでは実行できない、非常に有益な最適化が可能になります。
  • 実行時統計-JITコンパイルは実行時に行われるため、プログラムの実行中に統計を収集して、より適切な最適化を可能にします(特定の分岐が行われる確率を知るなど)。これにより、Java JITコンパイラーがC/C++コンパイラーよりも優れたコードを生成できるようになります(これは、最も可能性の高い分岐を事前に「推測」する必要があり、多くの場合間違っている可能性があります)。
  • 非常に優れたライブラリ-Javaランタイムには、(特にサーバー側アプリケーションの場合)優れたパフォーマンスを持つ非常によく書かれたライブラリのホストが含まれています。多くの場合、これらはあなたができるより優れています自分で書くか、C/C++を簡単に入手できます。

同時に、C/C++にはいくつかの利点もあります。

  • 高度な最適化を行うためのより多くの時間-C/C++コンパイルは1回行われるため、高度な最適化を行うように構成すると、かなりの時間をかけて高度な最適化を行うことができます。 Javaが同じことができなかった理由は理論的にはありませんが、実際にはJavaでコードを比較的速くJITコンパイルするため、JITコンパイラは「より単純な」最適化に焦点を当てます。
  • バイトコードで表現できない命令-Javaバイトコードは完全に汎用的な目的ですが、低レベルでは実行できないことがまだいくつかあります。バイトコードで実行します(チェックされていないポインター演算が良い例です!)。このようなトリックを(ab)使用することで、パフォーマンス上の利点を得ることができます。
  • 「安全性」の制約が少ない-Javaは、プログラムの安全性と信頼性を確保するためにいくつかの追加作業を行います。例としては、配列の境界チェック、特定の同時実行性の保証、nullポインターチェックがあります。 、キャストのタイプセーフなど。C/ C++でこれらを回避することにより、パフォーマンスを向上させることができます(ただし、これはおそらく悪い考えです!)

全体:

  • JavaとC/C++は同様の速度を実現できます
  • C/C++は、極端な状況ではおそらくわずかなエッジを持っています(たとえば、AAAゲーム開発者がまだそれを好んでいるのは当然のことです)
  • 実際には、上記のさまざまな要因が特定のアプリケーションでどのようにバランスをとるかによって異なります。
112
mikera

Javaランタイムisntはバイトコードを解釈します。むしろ、 と呼ばれるものを使用しますコンパイル 基本的に、プログラムが実行されると、プログラムはバイトコードを受け取り、特定のCPU用に最適化されたネイティブコードに変換します。

19
GrandmasterB

いいえ、Javaは決して速くないはずです。 C++で常にJavaを最初から実装することで、少なくとも同等のパフォーマンスを得ることができます。ただし、実際には:

  • JITは、エンドユーザーのマシンでコードをコンパイルし、実行中の正確なCPUに最適化できるようにします。ここではコンパイルのためのオーバーヘッドがありますが、集中的なアプリの場合は報われるかもしれません。多くの場合、実際のプログラムは、使用しているCPU用にコンパイルされていません。
  • Javaコンパイラーは、C++コンパイラーよりも自動的に最適化する方が優れている場合があります。あるいは、そうではないかもしれませんが、現実の世界では、物事は必ずしも完璧ではありません。
  • パフォーマンスの動作は、ガベージコレクションなどの他の要因によって異なる場合があります。 C++では、通常、オブジェクトを処理した直後にデストラクタを呼び出します。 Javaでは、参照を解放するだけで、実際の破棄が遅れます。これは、パフォーマンスの点で、ここにもそこにもない違いのもう1つの例です。もちろん、C++でGCを実装してそれで済ますことができると主張することはできますが、実際には、実行する/したい/できる人はほとんどいません。

余談ですが、これは80年代/ 90年代のCに関する議論を思い出させます。誰もが「Cはアセンブリと同じくらい速くなることができるのか?」と思っていました。基本的に、答えは次のとおりでした。紙面上ではありませんが、実際には、Cコンパイラはアセンブリプログラマの90%よりも効率的なコードを作成しました(まあ、それが少し成熟すると)。

19
Daniel B

しかし、割り当てはメモリ管理の半分にすぎません-割り当て解除は残りの半分です。ほとんどのオブジェクトの場合、直接的なガベージコレクションのコストはゼロであることがわかります。これは、コピーコレクターが死んだオブジェクトにアクセスしたりコピーしたりする必要はなく、生きているオブジェクトのみをコピーする必要があるためです。そのため、割り当て直後にガベージになるオブジェクトは、収集サイクルにワークロードを提供しません。

...

JVMは、開発者だけが知っていると想定していたものを理解するのが意外と上手です。 JVMにケースバイケースでスタック割り当てとヒープ割り当てのどちらかを選択させることにより、プログラマーにスタックに割り当てるかヒープに割り当てるかを悩ませることなく、スタック割り当てのパフォーマンス上の利点を得ることができます。

http://www.ibm.com/developerworks/Java/library/j-jtp09275/index.html

10
Landei

完全に最適化されたJavaプログラムが完全に最適化されたC++プログラムを打ち負かすことはめったにありませんが、メモリ管理などの違いにより、多くのアルゴリズムがJavaに慣用的に実装され、同じアルゴリズムが慣用的に実装されたものよりも高速になります。 C++で。

@Jerry Coffinが指摘したように、単純な変更によってコードを大幅に高速化できる場合が多くありますが、多くの場合、パフォーマンスの改善に価値があるために、1つの言語または他の言語で不必要な微調整を行いすぎることがあります。これはおそらく、C++よりもJavaの方が優れていることを示すgoodベンチマークに表示されるものです。

また、通常はそれほど重要ではありませんが、JavaなどのJIT言語ではC++ではできないパフォーマンスの最適化がいくつかあります。 Javaランタイムには改善を含めることができますafterコードがコンパイルされました。つまり、JITは最適化されたコードを生成して、新しい(または少なくとも異なる)CPU機能を利用できる可能性があります。このため、10年前のJavaバイナリは、10年前のC++バイナリよりもパフォーマンスが優れている可能性があります。

最後に、全体像を示す完全な型安全性は、非常にまれなケースですが、極端なパフォーマンスの向上をもたらします。 Singularity は、ほぼ完全にC#ベースの言語で記述された実験的なOSであり、ハードウェアプロセスの境界や高価なコンテキストスイッチを必要としないため、プロセス間通信とマルチタスクがはるかに高速です。

5
Rei Miyasaka

Tim HollowayによるJavaRanchへの投稿:

ここに原始的な例があります:マシンが数学的に決定されたサイクルで動作したとき、分岐命令には通常2つの異なるタイミングがありました。 1つは分岐が行われたとき、もう1つは分岐が行われなかったときです。通常、分岐なしのケースの方が高速でした。明らかに、これは、どちらのケースがより一般的であるかという知識に基づいてロジックを最適化できることを意味しました(「知っている」ことが常に実際にそうであるとは限らないという制約を受けます)。

JIT再コンパイルは、これをさらに一歩進めます。実際のリアルタイムの使用状況を監視し、実際に最も一般的なケースに基づいてロジックを切り替えます。また、ワークロードが変化した場合は、もう一度元に戻します。静的にコンパイルされたコードはこれを行うことができません。 Javaは、手動で調整したAssembly/C/C++コードよりもパフォーマンスが優れている場合があります。

ソース: http://www.coderanch.com/t/547458/Performance/Java/Ahead-Time-vs-Just-time

5
Thiago Negri

これは、C++プログラムをビルドするときに明示的にではなく、Javaプログラムの実行時に、マシンコードを生成する最後のステップが透過的に内部 JVMを実行するためです。

現代のJVMは、バイトコードをその場でネイティブマシンコードにコンパイルして可能な限り高速にするために、かなりの時間を費やしているという事実を考慮する必要があります。これにより、JVMは、実行中のプログラムのプロファイリングデータを知ることでさらに優れたコンパイラトリックを実行できます。

ゲッターを自動的にインライン化するだけで、JUMP-RETURNは値を取得するだけで済み、スピードアップします。

ただし、実際に高速プログラムを可能にしたのは、後でクリーンアップすることです。 Javaのガベージコレクションメカニズムは、Cの手動のmalloc-freeよりも速いです。多くの最新のmalloc-free実装は、下のガベージコレクターを使用します。

4
user1249

短い答え-そうではありません。それを忘れて、トピックは火か車輪と同じくらい古いです。 Javaまたは.NETはC/C++よりも高速であり、高速でもありません。最適化についてまったく考える必要がないほとんどのタスクでは十分高速です。フォームやSQL処理と同様に、でもそれで終わりです。

ベンチマーク、または能力のない開発者によって書かれた小さなアプリの場合、はい、最終結果はJava/.NETがおそらく近くなり、おそらくさらに高速になるでしょう。

実際には、スタックにメモリを割り当てる、または単にmemzonesを使用するなどの単純なことは、その場でJava/.NETを強制終了するだけです。

ガベージコレクションの世界は、すべての会計処理で一種のmemzoneを使用しています。 memzoneをCに追加すると、Cはその場ですぐに高速になります。特にJavaとCの「ハイパフォーマンスコード」のベンチマークの場合、次のようになります:

for(...)
{
alloc_memory//Allocating heap in a loop is verrry good, in't it?
zero_memory//Extra zeroing, we really need it in our performance code
do_stuff//something like memory[i]++
realloc//This is lovely speedup
strlen//loop through all memory, because storing string length is soo getting old
free//Java will do that outside out timing loop, but oh well, we're comparing apples to oranges here
}//loop 100000 times

C/C++(または新しい配置)でスタックベースの変数を使用してみてください。これらはsub esp, 0xffに変換されます。これは単一のx86命令であり、Javaでそれを打ちます-できません。 ..

ほとんどの場合、JavaとC++が比較されているベンチを見ると、次のような結果になりますか?間違ったメモリ割り当て戦略、予約なしの自己拡張コンテナ、複数の新しいコンテナです。これは新しいものではありません。パフォーマンス指向のC/C++コードにも近い。

また、よく読んでください: https://days2011.scala-lang.org/sites/days2011/files/ws3-1-Hundt.pdf

4
Coder

現実は、どちらもプログラマーに指示されたとおりの正確な順序で、プログラマーが指示したとおりに実行する高レベルのアセンブラーです。パフォーマンスの違いは非常に小さいため、すべての実用的な目的にとって重要ではありません。

言語は「遅い」のではなく、プログラマは遅いプログラムを書いた。研究の著者が彼の特定の斧を研ぎに出ていない限り、代替言語の最良の方法を使用して同じことを行うプログラムから(実用的な目的まで)1つの言語で最良の方法で書かれたプログラムは非常にまれです。

明らかに、ハードリアルタイムエンベデッドシステムのようなまれなEdgeケースに行く場合、言語の選択によって違いが生じる可能性がありますが、これはどれくらいの頻度で起こりますか?これらのケースの中で、どれほど頻繁に正しい選択が盲目的に明白ではないのか。

2
mattnz

次のリンクを参照してください...しかし、これはどのようにして可能ですか?解釈されたバイトコードは、コンパイルされた言語よりも速くなる可能性があるという私の心を揺さぶります。

  1. それらのブログ投稿は信頼できる証拠を提供していますか?
  2. それらのブログ投稿は決定的な証拠を提供していますか?
  3. それらのブログ投稿は、「解釈されたバイトコード」についての証拠さえ提供しますか?

キース・リーは、「明らかな欠陥」があると言いますが、それらの「明らかな欠陥」については何もしません。 2005年にこれらの古いタスクは破棄され、タスクに置き換えられました ベンチマークゲームに表示されます

キース・リー氏は、「C++のベンチマークコードを取得し、Java古くなったグレートコンピュータ言語シュートアウトからテストを実行し、テストを実行した」と述べていますが、実際には これらの古いテストの25

キース・リーは今、7年前のブログ投稿で何も証明しようとしていなかったと言いましたが、当時、彼は「Javaが遅いと言うのが遅いのを聞くのはうんざりでした。それはかなり速い...」ということは、当時彼が証明しようとしていたことがあったことを示唆しています。

クリスチャンフェルデは、「私はコードを作成したのではなく、テストを再実行した」と述べています。キースリーが選択したタスクとプログラムの測定値を公表するという彼の決定に対する責任から彼を免れたかのように。

25の非常に小さなプログラムの測定値でも、決定的な証拠は得られますか?

これらの測定値は、「混合モード」として実行されるプログラムの場合ですJava解釈されませんJava- "HotSpotの動作を覚えています。" Java「解釈されたバイトコード」を実行する方法を簡単に見つけることができます。強制的にJava toonlyバイトコードを解釈します-単に時間を計るJavaプログラムは-Xintオプションの有無にかかわらず実行されます。

2
igouy