16

最近、Java (および Scala などの JVM ベースの言語) のパフォーマンスが C/C++ コードに匹敵するという主張を複数見てきました。

たとえば、ScalaLab プロジェクトの説明から:

ネイティブおよび最適化された Java コードの速度に近づく Scala ベースのスクリプトの速度。したがって、C/C++ ベースの科学的コードに近いか、それよりも優れています。

これらの JVM 最適化の概要を教えてもらえますか? この主張を裏付ける、または実際の比較を提供する実際のベンチマークはありますか?

4

3 に答える 3

23

パフォーマンステクニック

まず、いくつかの JVM があるため、どのJVM について話しているかによって異なりますが、Oracle HotSpot を意味していると仮定します (いずれにせよ、他の最上位層の JVM は同様の手法を使用します)。

その JVM については、HotSpot の内部 wiki からのこのリストが優れた出発点となります (子ページでは、いくつかのより興味深い技術について詳しく説明されています)。トリックの長いリストを探しているだけなら、ウィキにもありますが、それらを理解するには、おそらく個々の用語をグーグルで検索する必要があります.

これらのすべてが最近実装されたわけではありませんが、いくつかの大きなもの (範囲チェック省略、エスケープ解析、スーパーワードの最適化) が実装されています - 少なくとも「最近」の大まかな定義については。

次に、C/C++ と Java の相対的なパフォーマンスの図を見てみましょう。また、上記の手法がギャップを狭めるのに役立つ理由、または場合によってはネイティブ コンパイル言語よりも Java と本質的な利点を実際に提供するのに役立つ理由を見てみましょう。

Java vs C/C++

大まかに言えば、最適化は、C や C++ などのネイティブ言語用の適切なコンパイラで見られるものと、Java/JVM 固有の機能や安全性チェックの影響を軽減するために必要なものを組み合わせたものです。なので:

  • オブジェクトのスタック割り当てなしを (ある程度) 軽減するエスケープ解析
  • 「すべての関数が仮想化されている」ことを軽減するインライン キャッシュとクラス階層分析
  • 「すべての配列アクセスが範囲チェックされる」ことを軽減する範囲チェックの排除

これらの JVM 固有の* 最適化の多くは、ネイティブ言語が対処する必要のないハードルに対処しているという点で、JVM をネイティブ言語と同等にするのに役立つだけです。ただし、いくつかの最適化は、静的にコンパイルされた言語では管理できないものです (または、場合によってはプロファイルに基づく最適化でのみ管理できますが、これはまれであり、いずれにせよ必然的に万能です):

  • 最もホットなコードのみの動的インライン化
  • 実際の分岐/スイッチ周波数に基づくコード生成
  • CPU/命令セット認識コードの動的生成 (コードのコンパイル後にリリースされた CPU 機能も含まれます!) 1
  • 実行されたことのないコードの省略
  • アプリケーション コードにインターリーブされたプリフェッチ命令の挿入
  • セーフポインティングによってサポートされているテクニックのファミリー全体

多くは正確なベンチマークに依存しますが、Java は gcc -O2 などの中程度の最適化レベルで優れた C++ コンパイラと同様の速度でコードを生成することが多いというのがコンセンサスのようです。HotSpot のような最新の JVM は、低レベルの配列トラバーサルと数学 (競合するコンパイラーがベクトル化していない限り - これを打ち負かすのは難しい)、または競合するコードが同様の数の割り当てを行っているときにオブジェクト割り当てが重いシナリオで優れている傾向があります。 (JVM オブジェクト割り当て + GC は一般に malloc よりも高速です) が、典型的な Java アプリケーションのメモリ ペナルティが要因である場合、スタック割り当てが頻繁に使用される場合、またはベクトル化コンパイラまたは組み込み関数がネイティブ コードに向かってスケールを傾ける場合には低下します。

Java と C のパフォーマンスについて検索すると、さまざまなレベルの厳密さでこの問題に取り組んでいる人がたくさん見つかります。これは私が最初に遭遇したもので、gcc と HotSpot の間の大まかな関係を示しているようです (この場合は -O3 でも)。この投稿とリンクされたディスカッションは、単一のベンチマークが各言語で複数の反復を経て相互に飛躍する方法を確認したい場合、おそらくより良い出発点であり、両側で最適化の限界のいくつかを示しています.

*実際には JVM 固有ではありません - ほとんどは、CLR のような他の安全な言語またはマネージ言語にも適用されます。


1この特定の最適化は、新しい命令セット (特に SIMD 命令ですが、他にもあります) がある程度の頻度でリリースされるにつれて、ますます重要になっています。自動ベクトル化は、一部のコードを大幅に高速化することができます。ここでは Java の速度が大幅に低下していますが、少なくとも少しは追いついています。

于 2013-04-22T03:40:32.980 に答える
13

もちろん、実際のパフォーマンスはベンチマークに依存し、アプリケーションによって異なります。しかし、少なくとも理論的には、JIT VM が静的にコンパイルされたコードと同じくらい高速になることは容易にわかります。

JIT コードの主な強みは、実行時にのみ知られている情報に基づいて最適化できることです。C で DLL に対してリンクする場合、毎回その関数を呼び出す必要があります。動的言語では、ジャスト イン タイム コンパイルのおかげで、実行時に読み込まれた関数であっても関数をインライン化できます。

もう 1 つの例は、ランタイム値に基づく最適化です。C/C++ では、プリプロセッサ マクロを使用してアサートを無効にし、このオプションを変更する場合は再コンパイルする必要があります。Java では、アサートは、プライベート ブール フィールドを設定し、コードに if 分岐を配置することによって処理されます。ただし、VM は、フラグの値に応じてアサート コードを含む、または含まないバージョンのコードをコンパイルできるため、パフォーマンスへの影響はほとんどまたはまったくありません。

もう 1 つの主要な VM の革新は、ポリモーフィック インライン化です。Idomatic Java は、ゲッターやセッターなどの小さなラッパー メソッドに重点を置いています。良いパフォーマンスを得るためには、それらをインライン化することが明らかに必要です。VM は、実際に 1 つの型のみが呼び出される一般的なケースでポリモーフィック関数をインライン化できるだけでなく、適切なコードにインライン キャッシュを含めることで、複数の異なる型を呼び出すコードをインライン化できます。コードが多くの異なるタイプで動作を開始した場合、VM はこれを検出し、低速の仮想ディスパッチにフォールバックできます。

もちろん、静的コンパイラはこれを行うことはできません。強力な静的分析は、これまでのところしか得られません。これは Java に限ったことではありませんが、最も明白な例です。Javascript 用の Google の V8 vm もかなり高速です。Pypy は Python で同じことを、Ruby では Rubinius で同じことを目指していますが、まだ十分ではありません (大企業の支援がある場合に役立ちます)。

于 2013-04-22T03:38:07.803 に答える