web-dev-qa-db-ja.com

Javaでfinalキーワードを使用すると、パフォーマンスが向上しますか?

Javaには、finalキーワードを使用できる場所がたくさんありますが、その使用は一般的ではありません。

例えば:

String str = "abc";
System.out.println(str);

上記の場合、strfinalにすることができますが、これは通常は省略されます。

メソッドがオーバーライドされない場合、finalキーワードを使用できます。同様に、継承されないクラスの場合。

これらのケースのいずれかまたはすべてでfinalキーワードを使用すると、実際にパフォーマンスが向上しますか?もしそうなら、どのように?説明してください。 finalの適切な使用がパフォーマンスにとって本当に重要な場合、キーワードを最大限に活用するためにJavaプログラマーはどのような習慣を開発する必要がありますか?

313
Abhishek Jain

通常はそうではありません。仮想メソッドの場合、HotSpotはメソッドに実際がオーバーライドされているかどうかを追跡し、メソッドがオーバーライドされていないという仮定のインライン化などの最適化を実行できます。 -メソッドをオーバーライドするクラスをロードするまで、その時点でそれらの最適化を元に戻す(または部分的に元に戻す)ことができます。

(もちろん、これは、HotSpotを使用していることを前提としていますが、最も一般的なJVMであるため、...)

私の考えでは、パフォーマンス上の理由ではなく、明確なデザインと読みやすさに基づいてfinalを使用する必要があります。パフォーマンス上の理由で何かを変更する場合は、最も明確なコードを変形させる前に適切な測定を実行する必要があります。これにより、達成された追加のパフォーマンスが貧弱な読みやすさ/設計に値するかどうかを判断できます。 (私の経験では、それだけの価値はありません; YMMV。)

編集:最後のフィールドが言及されているように、明確なデザインの観点から、とにかく彼らはしばしば良いアイデアであることを育てる価値があります。また、スレッド間の可視性に関して保証された動作を変更します。コンストラクターが完了すると、最終フィールドは他のスレッドですぐに表示されることが保証されます。私の経験ではおそらくこれがfinalの最も一般的な使用方法ですが、Josh Blochの「継承または禁止の設計」の経験則の支持者として、おそらくクラスではfinalをより頻繁に使用する必要があります...

256
Jon Skeet

簡単な答え:心配しないでください!

長い答え:

最終ローカル変数について話すときは、キーワードfinalを使用すると、コンパイラがコードを最適化するのに役立つことに注意してください静的。これにより、結果としてコードが高速になります。たとえば、次の例の最後の文字列a + bは、静的に(コンパイル時に)連結されます。

public class FinalTest {

    public static final int N_ITERATIONS = 1000000;

    public static String testFinal() {
        final String a = "a";
        final String b = "b";
        return a + b;
    }

    public static String testNonFinal() {
        String a = "a";
        String b = "b";
        return a + b;
    }

    public static void main(String[] args) {
        long tStart, tElapsed;

        tStart = System.currentTimeMillis();
        for (int i = 0; i < N_ITERATIONS; i++)
            testFinal();
        tElapsed = System.currentTimeMillis() - tStart;
        System.out.println("Method with finals took " + tElapsed + " ms");

        tStart = System.currentTimeMillis();
        for (int i = 0; i < N_ITERATIONS; i++)
            testNonFinal();
        tElapsed = System.currentTimeMillis() - tStart;
        System.out.println("Method without finals took " + tElapsed + " ms");

    }

}

結果?

Method with finals took 5 ms
Method without finals took 273 ms

Java Hotspot VM 1.7.0_45-b18でテスト済み。

それでは、実際のパフォーマンスの改善はいくらですか?あえて言わない。ほとんどの場合、おそらく限界(文字列の連結が完全に回避されるため、この合成テストでは〜270ナノ秒-まれなケース)ですが、高度に最適化されたユーティリティコードでは、mightが要因になります。いずれにせよ、元の質問に対する答えははい、パフォーマンスを改善するかもしれませんが、せいぜいわずかですがです。

コンパイル時の利点は別として、キーワードfinalの使用がパフォーマンスに測定可能な影響を与えるという証拠を見つけることができませんでした。

71
rustyx

はい、できます。finalがパフォーマンスを向上できるインスタンスを次に示します。

条件付きコンパイルは、特定の条件に基づいてコード行をクラスファイルにコンパイルしない手法です。これは、プロダクションビルドで大量のデバッグコードを削除するために使用できます。

以下を考慮してください。

public class ConditionalCompile {

  private final static boolean doSomething= false;

    if (doSomething) {
       // do first part. 
    }

    if (doSomething) {
     // do second part. 
    }

    if (doSomething) {     
      // do third part. 
    }

    if (doSomething) {
    // do finalization part. 
    }
}

DoSomething属性を最終属性に変換することにより、doSomethingが検出されるたびに、コンパイル時の置換規則に従ってfalseに置き換える必要があることをコンパイラーに伝えました。コンパイラの最初のパスは、コードを次のようにsomethingに変更します。

public class ConditionalCompile {

  private final static boolean doSomething= false;

    if (false){
       // do first part. 
    }

    if (false){
     // do second part. 
    }

    if (false){
      // do third part. 
    }

    if (false){
    // do finalization part. 

    }
}

これが完了すると、コンパイラーはそれをもう一度見て、コードに到達不能なステートメントがあることを確認します。最高品質のコンパイラを使用しているため、到達不能なすべてのバイトコードが好きではありません。したがって、それらは削除され、これで終わります:

public class ConditionalCompile {


  private final static boolean doSomething= false;

  public static void someMethodBetter( ) {

    // do first part. 

    // do second part. 

    // do third part. 

    // do finalization part. 

  }
}

したがって、過剰なコードや不要な条件チェックを減らします。

編集:例として、次のコードを見てみましょう。

public class Test {
    public static final void main(String[] args) {
        boolean x = false;
        if (x) {
            System.out.println("x");
        }
        final boolean y = false;
        if (y) {
            System.out.println("y");
        }
        if (false) {
            System.out.println("z");
        }
    }
}

このコードをJava 8でコンパイルし、javap -c Test.classで逆コンパイルすると、次のようになります。

public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method Java/lang/Object."<init>":()V
       4: return

  public static final void main(Java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iload_1
       3: ifeq          14
       6: getstatic     #16                 // Field Java/lang/System.out:Ljava/io/PrintStream;
       9: ldc           #22                 // String x
      11: invokevirtual #24                 // Method Java/io/PrintStream.println:(Ljava/lang/String;)V
      14: iconst_0
      15: istore_2
      16: return
}

コンパイルされたコードには、非最終変数xのみが含まれることに注意できます。これにより、少なくともこの単純なケースでは、最終的な変数がパフォーマンスに影響を与えることが証明されます。

56
mel3kings

IBMによると、クラスやメソッドには対応していません。

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

36
wmitchell

あなたは本当に2つの(少なくとも)異なるケースについて尋ねています:

  1. ローカル変数のfinal
  2. メソッド/クラスのfinal

ジョン・スキートはすでに2)に答えています。約1):

違いはないと思います。ローカル変数の場合、コンパイラーは変数が最終であるかどうかを推測できます(複数回割り当てられているかどうかを確認するだけで)。そのため、コンパイラが一度しか割り当てられない変数を最適化する必要がある場合、変数が実際にfinalとして宣言されているかどうかに関係なく、それを行うことができます。

finalmight protected/publicクラスフィールドに違いをもたらします。異なるクラス(ロードされていない場合もある)から発生する可能性があるため、フィールドが複数回設定されているかどうかをコンパイラが検出するのは非常に困難です。しかしそれでも、JVMはJonが説明する手法を使用できます(楽観的に最適化し、フィールドを変更するクラスがロードされると元に戻ります)。

要約すると、パフォーマンスを向上させる理由はわかりません。したがって、この種のマイクロ最適化は役に立ちそうにありません。確認のためにベンチマークを試すこともできますが、違いが出るとは思いません。

編集:

実際、TimoWestkämperの答えによれば、finalcanは、場合によってはクラスフィールドのパフォーマンスを向上させます。私は訂正します。

13
sleske

少なくとも多少の違いがあることを証明するために逆コンパイルされた実際のコードを実際に投稿した人がいないことに驚いています。

参考のために、これはjavacバージョン89、および10に対してテストされています。

このメソッドを想定してください:

public static int test() {
    /* final */ Object left = new Object();
    Object right = new Object();

    return left.hashCode() + right.hashCode();
}

このコードをそのままコンパイルすると、finalが存在する場合(final Object left = new Object();)と同じバイトコードexactが生成されます。

しかし、これは:

public static int test() {
    /* final */ int left = 11;
    int right = 12;
    return left + right;
}

生産物:

   0: bipush        11
   2: istore_0
   3: bipush        12
   5: istore_1
   6: iload_0
   7: iload_1
   8: iadd
   9: ireturn

finalをそのままにしておくと、以下が生成されます。

   0: bipush        12
   2: istore_1
   3: bipush        11
   5: iload_1
   6: iadd
   7: ireturn

コードはほとんど自明であり、コンパイル時定数がある場合、オペランドスタックに直接ロードされます(前の例がbipush 12; istore_0; iload_0を介して行うようにローカル変数配列に保存されません)-どのような誰もそれを変更できないので、感覚。

一方、2番目のケースでコンパイラがistore_0 ... iload_0を生成しない理由は私を超えており、スロット0が何らかの方法で使用されているわけではありません(変数配列をこのように縮小できますが、内部の詳細が欠落している可能性があります確かに言えない)

javacがどれだけ小さいかを考えると、このような最適化を見て驚いた。常にfinalを使用する必要がありますか? JMHテスト(最初にしたかった)を書くつもりもありません。diffはnsの順序であると確信しています(キャプチャできる場合)。これが問題になる可能性がある唯一の場所は、サイズのためにメソッドをインライン化できない場合です(finalを宣言すると、そのサイズが数バイト縮小します)。

対処する必要のあるfinalsがさらに2つあります。 1つは、メソッドがfinalの場合(JITの観点から)、そのようなメソッドはmonomorphic-そして、これらは 最も愛されている = JVMによるもの。

次に、finalインスタンス変数があります(すべてのコンストラクターで設定する必要があります)。これらは正しく公開された参照を保証するので重要です ここで少し触れたように そしてJLSによって正確に指定されます。

11
Eugene

注:Javaエキスパートではありません

Javaを正しく覚えていれば、finalキーワードを使用してパフォーマンスを改善する方法はほとんどありません。 「良いコード」-デザインと読みやすさのために存在することは常に知っています。

5
Neowizard

私は専門家ではありませんが、finalキーワードをクラスまたはメソッドに追加して、上書きしない場合は変数をそのままにしておく必要があると思います。そのようなことを最適化する方法がある場合は、コンパイラがそれを行います。

1
Crozin

実際、OpenGL関連のコードをテストしているときに、プライベートフィールドでfinal修飾子を使用するとdegrade performanceになることがわかりました。テストしたクラスの始まりは次のとおりです。

public class ShaderInput {

    private /* final */ float[] input;
    private /* final */ int[] strides;


    public ShaderInput()
    {
        this.input = new float[10];
        this.strides = new int[] { 0, 4, 8 };
    }


    public ShaderInput x(int stride, float val)
    {
        input[strides[stride] + 0] = val;
        return this;
    }

    // more stuff ...

そして、これは、ShaderInputクラスの中で、さまざまな代替のパフォーマンスをテストするために使用したメソッドです。

public static void test4()
{
    int arraySize = 10;
    float[] fb = new float[arraySize];
    for (int i = 0; i < arraySize; i++) {
        fb[i] = random.nextFloat();
    }
    int times = 1000000000;
    for (int i = 0; i < 10; ++i) {
        floatVectorTest(times, fb);
        arrayCopyTest(times, fb);
        shaderInputTest(times, fb);
        directFloatArrayTest(times, fb);
        System.out.println();
        System.gc();
    }
}

VMがウォームアップされた3回目の反復の後、一貫してこれらの数字を取得しましたなし最後のキーワードWord:

Simple array copy took   : 02.64
System.arrayCopy took    : 03.20
ShaderInput took         : 00.77
Unsafe float array took  : 05.47

With最後のキーワード:

Simple array copy took   : 02.66
System.arrayCopy took    : 03.20
ShaderInput took         : 02.59
Unsafe float array took  : 06.24

ShaderInputテストの図に注意してください。

フィールドをパブリックにするかプライベートにするかは関係ありません。

ちなみに、さらに厄介なことがいくつかあります。 ShaderInputクラスは、finalキーワードを使用しても、他のすべてのバリアントよりも優れています。これは驚くべきb/cであり、基本的にはfloat配列をラップするクラスですが、他のテストでは直接配列を操作します。これを理解する必要があります。 ShaderInputの流れるようなインターフェイスと関係がある可能性があります。

また、System.arrayCopyは、実際には、forループで要素を1つの配列から他の配列に単純にコピーするよりも、小さな配列の方が明らかに遅いです。また、Sun.misc.Unsafe(およびここには示されていない直接のJava.nio.FloatBuffer)を使用するとabysmallyが実行されます。

1
user3663845

finalで宣言されたメンバーは、プログラム全体で利用可能になります。これは、非ファイナルメンバーとは異なり、これらのメンバーがプログラムで使用されていない場合でも、ガベージコレクターによって処理されないため、メモリ管理が不適切なためにパフォーマンスの問題が発生する可能性があるためです。

0
nams