JVMでのメソッドのインライン化

1. 前書き

このチュートリアルでは、Java Virtual Machineでのメソッドのインライン化とその機能について説明します。
また、JVMからのインライン化に関連する情報を取得および読み取る方法、およびコードを最適化するためにこの情報を使用して何ができるかについても説明します。

2. メソッドのインライン化とは

基本的に、*インライン化は、最も頻繁に実行されるメソッドの呼び出しをその本体で置き換えることにより、実行時にコンパイル済みソースコードを最適化する方法です。*
コンパイルも含まれますが、従来の_javac_コンパイラーではなく、JVM自体によって実行されます。 より正確には、* JVMの一部であるJust-In-Time(JIT)コンパイラ*の責任です。 _javac_はバイトコードのみを生成し、JITに魔法をかけてソースコードを最適化します。
このアプローチの最も重要な結果の1つは、古いJavaを使用してコードをコンパイルすると、新しいJVMで同じ._class_ファイルが高速になることです。 この方法では、ソースコードを再コンパイルする必要はなく、Javaを更新するだけです。

3. JITの仕組み

基本的に、* JITコンパイラーは、メソッド呼び出しのオーバーヘッドを回避できるように、頻繁に呼び出すメソッドをインライン化しようとします*。 メソッドをインライン化するかどうかを決定するときには、2つのことを考慮します。
まず、カウンターを使用して、メソッドを呼び出した回数を追跡します。 メソッドが特定の回数以上呼び出されると、「ホット」になります。 このしきい値はデフォルトで10,000に設定されていますが、Javaの起動時にJVMフラグを使用して設定できます。 時間がかかり、巨大なバイトコードが生成されるため、すべてをインライン化することは絶対に避けたいです。
インライン化は、安定した状態になったときにのみ行われることに注意してください。 これは、JITコンパイラーに十分なプロファイリング情報を提供するために、実行を数回繰り返す必要があることを意味します。
さらに、「ホット」であることは、メソッドがインライン化されることを保証しません。 大きすぎる場合、JITはインライン化しません。 許容サイズは_-XX:FreqInlineSize = _フラグによって制限されます。このフラグは、メソッドでインライン化するバイトコード命令の最大数を指定します。
それでも、このフラグがどのような影響を与える可能性があるのか​​が確実に分からない限り、このフラグのデフォルト値を変更しないことを強くお勧めします。 デフォルト値はプラットフォームに依存します– 64ビットLinuxの場合、325です。
  • JITインライン_static private _ * *または_final_メソッド全般。 また、* _ public_メソッドもインライン化の候補ですが、すべてのパブリックメソッドが必ずしもインライン化されるわけではありません。 JVMは、このようなメソッドの実装が1つしかないことを判断する必要があります*。 サブクラスを追加するとインライン化が妨げられ、パフォーマンスは必然的に低下します。

4. ホットメソッドの検索

JITが何をしているかを推測したくはありません。 したがって、どのメソッドがインライン化されているか、インライン化されていないかを確認する方法が必要です。 起動時にいくつかの追加のJVMフラグを設定することにより、これを簡単に達成し、これらすべての情報を標準出力に記録できます。
-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining
JITコンパイルが発生すると、最初のフラグがログに記録されます。 2番目のフラグは、_- XX:PrintInlining_を含む追加のフラグを有効にします。PrintInlining_は、インライン化されるメソッドと場所を出力します。
これにより、インライン化されたメソッドがツリー形式で表示されます。 葉には注釈が付けられ、次のオプションのいずれかでマークされます。
  • inline(hot) –このメソッドはhotとしてマークされ、インライン化されます

  • too big –メソッドはホットではありませんが、生成されたバイトコードもホットです
    大きすぎるため、インラインではありません

  • hotメソッドが大きすぎる –これはホットメソッドですが、インライン化されていません
    バイトコードが大きすぎるため

  • 3番目の値に注意を払い、「ホットメソッドが大きすぎる」というラベルのメソッドを最適化する必要があります。*

    一般に、非常に複雑な条件ステートメントを持つホットメソッドを見つけた場合、__ if -__ statementのコンテンツを分離し、粒度を上げてJITがコードを最適化できるようにする必要があります。 _switch_および__for -__ loopステートメントについても同様です。
    手動によるメソッドのインライン化は、コードを最適化するために行う必要のないものであると結論付けることができます。 JVMはそれをより効率的に実行しますが、コードを長くして従うのを難しくする可能性があります。

4.1. 例

これを実際に確認する方法を見てみましょう。 最初に、最初の_N_個の連続する正の整数の合計を計算する単純なクラスを作成します。
public class ConsecutiveNumbersSum {

    private long totalSum;
    private int totalNumbers;

    public ConsecutiveNumbersSum(int totalNumbers) {
        this.totalNumbers = totalNumbers;
    }

    public long getTotalSum() {
        totalSum = 0;
        for (int i = 0; i < totalNumbers; i++) {
            totalSum += i;
        }
        return totalSum;
    }
}
次に、簡単なメソッドがクラスを使用して計算を実行します。
private static long calculateSum(int n) {
    return new ConsecutiveNumbersSum(n).getTotalSum();
}
最後に、メソッドを何度も呼び出して、何が起こるかを確認します。
for (int i = 1; i < NUMBERS_OF_ITERATIONS; i++) {
    calculateSum(i);
}
最初の実行では、1,000回実行します(上記のしきい値10,000未満)。 _calculateSum()_メソッドの出力を検索しても、見つかりません。 これは、十分な回数呼び出さなかったためです。
繰り返し回数を15,000に変更し、出力を再度検索すると、次のように表示されます。
664 262 % com.baeldung.inlining.InliningExample::main @ 2 (21 bytes)
  @ 10   com.baeldung.inlining.InliningExample::calculateSum (12 bytes)   inline (hot)
今回は、メソッドがインライン化の条件を満たし、JVMがインライン化したことがわかります。
メソッドが大きすぎる場合、繰り返しの回数に関係なく、JITはメソッドをインライン化しないことに再び言及することは注目に値します。 これを確認するには、アプリケーションの実行時に別のフラグを追加します。
-XX:FreqInlineSize=10
前の出力でわかるように、メソッドのサイズは12バイトです。 _-XX:FreqInlineSize_フラグは、インライン化に適格なメソッドサイズを10バイトに制限します。 その結果、今回はインライン化を行わないでください。 そして実際、出力をもう一度見るとこれを確認できます。
330 266 % com.baeldung.inlining.InliningExample::main @ 2 (21 bytes)
  @ 10   com.baeldung.inlining.InliningExample::calculateSum (12 bytes)   hot method too big
ここでは説明のためにフラグの値を変更しましたが、どうしても必要な場合を除き、_- XX:FreqInlineSize_フラグのデフォルト値を変更しないことを推奨する必要があります。

5. 結論

この記事では、JVMでのメソッドのインライン化と、JITがそれを行う方法について説明しました。 メソッドがインライン化に適格かどうかを確認する方法について説明し、インライン化するには大きすぎる頻繁に呼び出される長いメソッドのサイズを小さくすることで、この情報を利用する方法を提案しました。
最後に、実際にホットメソッドを特定する方法を示しました。
この記事に記載されているコードスニペットはすべて、https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-jvm [GitHubリポジトリ]にあります。