Javaの最終メソッドは自動的にインライン化されますか?
多くの本はイエスと言っています 多くの本はノーと言っています!!!
メソッドのインライン化は、javacではなくJITコンパイラによって実行されます。
最新のJITコンパイラ(ホットスポットを含む)は、多くの場合、非最終メソッドでさえインライン化し、必要に応じて最適化を適切に「元に戻す」ことができます。彼らは基本的に恐ろしく賢いです。
つまり、VMに完全に依存します。私の意見では、パフォーマンスではなく、最もクリーンなコードを生成するものに基づいて、メソッドを最終的なものにするか、そうでないようにする必要があります。私は個人的に「継承のための設計またはそれを禁止する」のファンですが、それは別の議論です:)
興味深い質問で、さらに調査するように促されました。私が見つけた2つの興味深い発言 -
多くのヒントの暗示に反して、final として宣言されたメソッドは、実行時に非 final 宣言を持つ可能性があるため、コンパイラによって安全にインライン化できません。
その理由を理解するために、コンパイラがクラス A とサブクラス B、およびサブサブクラス C を見て、C にインライン化された A の最終メソッドを確認するとします。しかし、実行時に A と B にロードされたバージョンは異なり、メソッドはA では最終ではなく、B でオーバーライドされます。その後、C は誤ってインライン化されたバージョンを使用します。T
そして、もう少し信頼できるように、sun のホワイトペーパーから、メソッドを仮想のままにしておくことができると書いています。
Java HotSpot VM は大部分の仮想メソッド呼び出しを自動的にインライン化できるため、このパフォーマンスの低下は劇的に軽減され、多くの場合、完全に解消されます。
これは、メカニズムに関するより直接的な参照です。
「コンパイル中にインライン化されますか」という意味の場合、いいえ、そうではありません。
ただし、静的なfinalフィールドは、プリミティブや文字列など、コンパイラによってインライン化される場合があります。
final
コンパイラやVMに何かをインライン化するように指示するのではなく、デザインにセマンティクスを追加することです。最新のVMは、最終的なメソッドだけよりもはるかに多くインライン化されているためfinal
、ランタイムの最適化を使用したり、予測しすぎたりするのは適切な理由ではありません。
これは、実行しているJVMの実装によって異なると思います。確かに、メソッドをfinalにすることで、コンパイラーはそのような実装を微調整することができます。しかし、そうであるかどうかは、他の要因にも依存する可能性があります-たとえば、その巨大な方法などの場合はどうなりますか?
インライン化するかどうかの Hotspot の決定は、多くの考慮事項に依存して非常に複雑ですが、メソッドが「最終」とマークされているかどうかはその 1 つではないと思います。その理由は、そのメソッドの複数の実装が VM にロードされているかどうかを既に認識しているためです。したがって、そのような実装が許可されているかどうかも知る必要はありません。
いずれにせよ、インライン化されるのは非常に小さくて単純なメソッドだけであり、それらすべてではありません。
Jonが言ったように、インライン化はバイトコード生成レベルではなくJITコンパイラによって(必要に応じて)行われます。同じコードが cpu l1 キャッシュに複数回存在し、他のコード用のスペースが削除される状況が発生する可能性があるため、インライン化によってパフォーマンスが低下する場合があることにも注意してください。L1 キャッシュ ミスは、キャッシュされた関数へのジャンプ以上にパフォーマンスに影響を与える可能性があります。
定数 (別名 final static var) は代わりにインライン化されます。
これを見て確認
public class InlineTest {
final static int add(int x, int y) {
return x + y;
}
}
public class Main {
static final int DIVISOR = 7;
static void main(String[] args){
final int a = new Integer(args[0]);
final int b = new Integer(args[1]);
if (InlineTest.add(a, b) % DIVISOR == 0)
System.exit(InlineTest.add(a, b));
System.out.print("The sum is " + InlineTest.add(a, b));
}
}
これは次のように翻訳されています。
0 new #2 <java/lang/Integer>
3 dup
4 aload_0
5 iconst_0
6 aaload
7 invokespecial #3 <java/lang/Integer/<init>(Ljava/lang/String;)V>
10 invokevirtual #4 <java/lang/Integer/intValue()I>
13 istore_1
14 new #2 <java/lang/Integer>
17 dup
18 aload_0
19 iconst_1
20 aaload
21 invokespecial #3 <java/lang/Integer/<init>(Ljava/lang/String;)V>
24 invokevirtual #4 <java/lang/Integer/intValue()I>
27 istore_2
28 iload_1
29 iload_2
30 invokestatic #5 <com/gamasoft/InlineTest/add(II)I>
33 bipush 7
35 irem
36 ifne 47 (+11)
39 iload_1
40 iload_2
41 invokestatic #5 <com/gamasoft/InlineTest/add(II)I>
44 invokestatic #7 <java/lang/System/exit(I)V>
47 getstatic #8 <java/lang/System/out Ljava/io/PrintStream;>
50 new #9 <java/lang/StringBuilder>
53 dup
54 invokespecial #10 <java/lang/StringBuilder/<init>()V>
57 ldc #11 <The sum is >
59 invokevirtual #12 <java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;>
62 iload_1
63 iload_2
64 invokestatic #5 <com/gamasoft/InlineTest/add(II)I>
67 invokevirtual #13 <java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;>
70 invokevirtual #14 <java/lang/StringBuilder/toString()Ljava/lang/String;>
73 invokevirtual #15 <java/io/PrintStream/print(Ljava/lang/String;)V>
76 return
静的関数 InlineTest.add が複数回呼び出されていることがわかりますinvokestatic