Czy użycie ostatniego słowa kluczowego w Javie poprawia wydajność?

W Javie widzimy wiele miejsc, w których można użyć słowa kluczowego final, ale jego użycie jest rzadkie.

Na przykład:

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

W powyższym przypadku, {[2] } może być final, ale jest to zwykle pomijane.

Gdy metoda nigdy nie zostanie nadpisana, możemy użyć ostatniego słowa kluczowego. Podobnie jest w przypadku klasy, która nie będzie dziedziczona.

Czy użycie ostatniego słowa kluczowego w którymś z tych przypadków naprawdę poprawia wydajność? Jeśli tak, to jak? Proszę wyjaśnić. Jeśli właściwe użycie final naprawdę ma znaczenie dla wydajności, jakie nawyki powinien rozwinąć programista Java, aby jak najlepiej wykorzystać słowo kluczowe?

 288
Author: OldPeculier, 2010-11-25

12 answers

Zazwyczaj nie. W przypadku metod wirtualnych, HotSpot śledzi, czy metoda faktycznie została nadpisana, i jest w stanie wykonać optymalizacje, takie jak inlining przy założeniu , że metoda nie została nadpisana - dopóki nie załaduje klasy, która nadpisuje metodę, w którym to momencie może cofnąć (lub częściowo cofnąć) te optymalizacje.

(Oczywiście, zakładając, że używasz hotspota - ale jest to zdecydowanie najczęstszy JVM, więc...)

To my mind you powinien używać final w oparciu o przejrzysty projekt i czytelność, a nie ze względu na wydajność. Jeśli chcesz coś zmienić ze względu na wydajność, powinieneś wykonać odpowiednie pomiary przed wygięciem najczystszego kodu z kształtu - w ten sposób możesz zdecydować, czy jakakolwiek dodatkowa wydajność jest warta gorszej czytelności/konstrukcji. (Z mojego doświadczenia prawie nigdy nie warto; YMMV.)

EDIT: jak już wspomniano Ostatnie pola, warto wspomnieć, że często są one w każdym razie dobry pomysł, jeśli chodzi o przejrzysty projekt. Zmieniają one również gwarantowane zachowanie w zakresie widoczności między wątkami: po zakończeniu konstruktora, wszystkie końcowe pola są natychmiast widoczne w innych wątkach. Jest to prawdopodobnie najczęstsze użycie final w moim doświadczeniu, chociaż jako zwolennik Zasady Josha Blocha "Zaprojektuj dziedziczenie lub Zabroń go", powinienem prawdopodobnie częściej używać final w klasach...

 240
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-25 17:35:55

Krótka odpowiedź: nie martw się o to!

Długa ODPOWIEDŹ:

Mówiąc o ostatecznych zmiennych lokalnych należy pamiętać, że użycie słowa kluczowego final pomoże kompilatorowi zoptymalizować kod statycznie, co może w efekcie doprowadzić do szybszego kodu. Na przykład, końcowe ciągi a + b w poniższym przykładzie są skonkatenowane statycznie (w czasie kompilacji).

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");

    }

}

Wynik?

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

Testowane Na Java Hotspot VM 1.7.0_45-b18.

Więc ile czy rzeczywista poprawa wydajności? Nie śmiem mówić. W większości przypadków prawdopodobnie marginalne (~270 nanosekund w tym teście syntetycznym, ponieważ w ogóle unika się konkatenacji ciągu-rzadki przypadek), ale w wysoce zoptymalizowanym kodzie użyteczności może być czynnikiem. W każdym razie odpowiedź na pierwotne pytanie brzmi tak, może to poprawić wydajność, ale w najlepszym razie marginalnie .

Pomijając korzyści związane z czasem kompilacji, nie mogłem znaleźć żadnych dowodów na to, że użycie słowa kluczowego final ma wymierny wpływ na wydajność.

 64
Author: rustyx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-05 13:52:43

Tak, może. Oto przykład, w którym final może zwiększyć wydajność:

Kompilacja warunkowa jest techniką, w której wiersze kodu nie są kompilowane do pliku klasy w oparciu o określony warunek. Może to być wykorzystane do usunięcia ton kodu debugowania w kompilacji produkcyjnej.

Rozważ co następuje:

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. 
    }
}

Konwertując atrybut doSomething na ostateczny atrybut, powiedziałeś kompilatorowi, że kiedykolwiek widzi doSomething, to powinien zastąpić go false zgodnie z regułami zastępowania w czasie kompilacji. Pierwsze przejście kompilatora zmienia kod na coś w ten sposób:

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. 

    }
}

Gdy to zrobi, kompilator ponownie na to spojrzy i zobaczy, że w kodzie znajdują się instrukcje nieosiągalne. Ponieważ pracujesz z najwyższej jakości kompilatorem, nie podoba Ci się te wszystkie nieosiągalne kody bajtowe. Więc je usuwa, a Ty kończysz z tym:

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. 

  }
}

Tym samym redukując wszelkie nadmierne kody, lub jakiekolwiek niepotrzebne sprawdzanie warunkowe.

Edytuj: Jako przykład, weźmy następujący kod:

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");
        }
    }
}

Kompilując ten kod z Java 8 i dekompilując z javap -c Test.class otrzymujemy:

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
}

Możemy zauważyć, że skompilowany kod zawiera tylko nie-ostateczną zmienną x. To dowodzi, że ostateczne zmienne mają wpływ na wydajność, przynajmniej w tym prostym przypadku.

 51
Author: mel3kings,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-06 21:27:55
 36
Author: wmitchell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-25 17:14:08

Naprawdę pytasz o dwa (przynajmniej) różne przypadki:

  1. final dla zmiennych lokalnych
  2. final dla metod/klas

Jon Skeet już odpowiedział 2). O 1):

Myślę, że to nie robi różnicy; dla zmiennych lokalnych kompilator może wydedukować, czy zmienna jest ostateczna, czy nie (po prostu sprawdzając, czy jest przypisana więcej niż raz). Więc jeśli kompilator chciał zoptymalizować zmienne, które są przypisane tylko raz, może to zrobić nie ma znaczenie, czy zmienna jest rzeczywiście zadeklarowana final, czy nie.

final Może zrobić różnicę dla pól klasy chronionej / publicznej; tam kompilator bardzo trudno jest dowiedzieć się, czy pole jest ustawiane więcej niż raz, ponieważ może się to zdarzyć z innej klasy (która nawet nie została załadowana). Ale nawet wtedy JVM mógłby użyć techniki opisanej przez Jona (Optymalizuj optymistycznie, Przywróć, jeśli załadowana jest klasa, która zmienia pole).

Podsumowując, I nie widzę powodu, dla którego miałoby to pomóc w wydajności. Więc tego rodzaju mikro-optymalizacja jest mało prawdopodobne, aby pomóc. Możesz spróbować go porównać, aby się upewnić, ale wątpię, że to coś zmieni.

Edit:

Według odpowiedzi Timo Westkämpera, final Może poprawić wydajność dla pól klas w niektórych przypadkach. Przyznaję się do błędu.
 12
Author: sleske,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-25 17:19:15

Jestem zdumiony, że nikt nie opublikował jakiegoś prawdziwego kodu, który jest dekompilowany, aby udowodnić, że istnieje przynajmniej niewielka różnica.

Dla odniesienia to zostało przetestowane przeciwko javac wersja 8, 9 i 10.

Załóżmy, że ta metoda:

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

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

Skompilowanie tego kodu tak jak jest, daje dokładny ten sam kod bajtowy, jak wtedy, gdy final byłby obecny (final Object left = new Object();).

Ale to jeden:

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

Produkuje:

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

Pozostawienie final aby być obecnym daje:

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

Kod jest dość oczywisty, w przypadku gdy istnieje stała czasowa kompilacji, zostanie on załadowany bezpośrednio na stos operandu (nie będzie przechowywany w tablicy zmiennych lokalnych, jak w poprzednim przykładzie za pomocą bipush 12; istore_0; iload_0) - co ma sens, ponieważ nikt nie może go zmienić.

Z drugiej strony, dlaczego w drugim przypadku kompilator nie produkuje istore_0 ... iload_0 jest poza nie jest tak, że slot 0 jest używany w jakikolwiek sposób (może zmniejszyć tablicę zmiennych w ten sposób, ale może im brakuje niektórych wewnętrznych szczegółów, nie mogę powiedzieć na pewno)

Byłem zaskoczony taką optymalizacją, biorąc pod uwagę, jak maluchy javac robią. Czy powinniśmy zawsze używać final? Nie zamierzam nawet pisać JMH testu (który chciałem początkowo), jestem pewien, że diff jest w kolejności ns (jeśli to możliwe, aby być przechwycone w ogóle). Jedyne miejsce, gdzie to może być problem polega na tym, że metoda nie może być inlinowana ze względu na jej rozmiar (a zadeklarowanie final zmniejszyłoby ten rozmiar o kilka bajtów).

Są jeszcze dwa final, które należy rozwiązać. Po pierwsze, gdy metoda jest final (z perspektywy JIT), taka metoda jest monomorficzna - i są to najbardziej ukochane przez JVM.

Wtedy są zmienne instancji final (które muszą być ustawione w każdym konstruktorze); są one ważne, ponieważ będą gwarantujemy poprawną publikację referencji, , Jak nieco tutaj , a także dokładnie określone przez JLS.

 8
Author: Eugene,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-21 22:10:22

Note: Not a java expert

Jeśli dobrze pamiętam moją Javę, nie byłoby zbyt wiele sposobów na poprawę wydajności przy użyciu ostatniego słowa kluczowego. Zawsze wiedziałem, że istnieje dla "dobrego kodu" - projektowania i czytelności.

 5
Author: Neowizard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-25 17:16:50

Nie jestem ekspertem, ale przypuszczam, że powinieneś dodać final słowo kluczowe do klasy lub metody, jeśli nie zostanie nadpisane i zostawić zmienne w spokoju. Jeśli istnieje jakiś sposób, aby zoptymalizować takie rzeczy, kompilator zrobi to za Ciebie.

 1
Author: Crozin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-25 17:22:17

Podczas testowania kodu związanego z OpenGL odkryłem, że użycie ostatecznego modyfikatora na prywatnym polu może obniżyć wydajność. Oto początek klasy, którą testowałem:

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 ...

I jest to metoda, której użyłem do testowania wydajności różnych alternatyw, wśród których Klasa 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();
    }
}

Po 3 iteracji, z rozgrzanym VM, konsekwentnie otrzymałem te liczby bez ostatniego słowa kluczowego:

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

Z {[6] } ostatnie słowo kluczowe:

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

Zwróć uwagę na liczby dla testu ShaderInput.

Nie miało znaczenia, czy uczyniłem pola publicznymi czy prywatnymi. Nawiasem mówiąc, jest jeszcze kilka zaskakujących rzeczy. Klasa Shader Input przewyższa wszystkie inne warianty, nawet przy użyciu ostatniego słowa kluczowego. Jest to niezwykłe b / C w zasadzie jest to klasa owijająca tablicę zmiennoprzecinkową, podczas gdy inne testy bezpośrednio manipulują tablicą. Muszę to rozgryźć. Może mieć coś wspólnego z Płynny interfejs ShaderInput.

Również System.arrayCopy w rzeczywistości jest nieco wolniejszy dla małych tablic niż zwykłe kopiowanie elementów z jednej tablicy do drugiej w pętli for. I za pomocą słońca.misc.Niebezpieczne (jak również bezpośrednie java.nio.FloatBuffer, nie pokazany tutaj) występuje fatalnie.

 1
Author: user3663845,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-14 21:17:17

Definitywnie tak, jeśli zmienna zamienia się na stałą,

Jak wiemy kompilator Javy konwertuje takie zmienne końcowe na stałe, które mogą

Jako koncepcja stałego kompilatora Javy bezpośrednio zastąp wartość referencją w czasie kompilacji

W Javie zmienne są stałe, jeśli jest to typ string lub primitive, który jest podany bez żadnego procesu uruchomieniowego

W Przeciwnym Razie to tylko ostateczna (non changeble) zmienna,

Użycie & constatnt jest zawsze szybsze niż Referencja.

Więc jeśli to możliwe, użyj stałych w dowolnym języku programowania dla lepszej wydajności

 0
Author: Dharmendrasinh Chudasama,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-16 10:23:24

Członkowie zadeklarowani z finałem będą dostępni w całym programie, ponieważ w przeciwieństwie do tych niefinalnych, gdyby członkowie Ci nie byli używani w programie, nadal nie byliby zajmowani przez Garbage Collector może więc powodować problemy z wydajnością z powodu złego zarządzania pamięcią.

 0
Author: nams,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-11 09:05:54

final słowo kluczowe może być używane w Javie na pięć sposobów.

  1. klasa jest ostateczna
  2. zmienna odniesienia jest ostateczna
  3. zmienna lokalna jest ostateczna
  4. metoda jest ostateczna

Klasa jest ostateczna: klasa jest ostateczna oznacza, że nie możemy być rozszerzeni lub dziedziczenie oznacza, że dziedziczenie nie jest możliwe.

Podobnie - obiekt jest ostateczny: jakiś czas nie modyfikujemy wewnętrznego stanu obiektu, więc w takim przypadku możemy określić, że obiekt jest ostatecznym obiektem.finał obiektu oznacza, że nie zmienna również ostateczna.

Gdy zmienna referencyjna jest ostateczna, nie można jej przypisać do innego obiektu. Ale może zmieniać zawartość obiektu tak długo, jak jego pola nie są ostateczne

 -3
Author: adityaraj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-28 10:22:03