パフォーマンステクニック
まず、いくつかの 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 の速度が大幅に低下していますが、少なくとも少しは追いついています。