2015-12-01 11 views
5

Jakie optymalizacje wykonuje Java Runtime w poniższym fragmencie kodu? Kod bajtowy nie ujawnia żadnej optymalizacji, ale uważam, że Java powinna przyjąć ostatnią wartość pętli for bez uruchamiania całej pętli for, ponieważ String jest podstawową klasą Java.Java dla optymalizacji pętli

UWAGA. to pytanie zadano na testach klasy; nie mogłem jednak dostarczyć wystarczających dowodów, by poprzeć moje roszczenie.

public class Main { 

    public static void main(String[] args) { 
     String str = null; 
     for (long i = 0; i < 10000000000L; i++) { 
      str = new String("T"); 
     } 

     System.out.println(str); 
    } 
} 
+4

Zasadniczo żadna optymalizacja nie jest wykonywana na poziomie bajtów; prawie wszystko w czasie pracy. JIT to najczęściej magiczna czarna skrzynka, której nie można zagwarantować, ale nie jest wykluczone, że cała pętla zostanie wyeliminowana. –

+0

Możesz zobaczyć, jak JIT "obsługuje" twój kod, korzystając z następujących opcji JVM: '-XX: + PrintCompilation -XX: + UnlockDiagnosticVMOptions -XX: + PrintInlining'. – fge

+0

@fge Odszyfrowanie, że wyjście jest sporym wyzwaniem. Nie jestem do końca pewien, co się tam dzieje. – bluejamesbond

Odpowiedz

1

Chociaż nie mogę mówić dokładnie to, co kompilator JIT robi, optymalizacja prosicie to zrobić (w celu określenia, że ​​jest to bezpieczne, aby pominąć całkowicie ciało pętli) jest rzeczywiście bardzo trudne do zrobienia , więc bardzo wątpię, że to się stało. Jest to prawda, niezależnie od tego, że String jest "podstawową klasą Java".

Aby lepiej zrozumieć, najpierw załóżmy, że zamiast String tworzymy wystąpienia dowolnej klasy Foo. Bezpiecznie byłoby pominąć tworzenie wszystkich tych obiektów Foo, gdybyśmy znali dwie rzeczy: wywoływanie new Foo() nie powodowało żadnych widocznych efektów ubocznych; i że żadne odniesienia do Foo nie "uciekły" z ciała pętli.

Obserwowalnym efektem ubocznym byłoby coś w rodzaju ustawiania wartości statycznego elementu (np. Jeśli klasa Foo zachowała statyczną liczbę wszystkich wywołań). Przykładem ucieczki odniesienia byłoby, gdyby zmienna this wewnątrz została przekazana gdzie indziej.

Należy zauważyć, że nie wystarczy spojrzeć na Foo(), trzeba spojrzeć na konstruktor nadklasy Foo "(i całą drogę w górę łańcucha do obiektu). A następnie musisz spojrzeć na cały kod, który zostanie wykonany po inicjalizacji każdego z tych obiektów. A następnie spójrz na cały kod, który zostanie wywołany przez ten kod. Byłaby to ogromna ilość analiz do zrobienia "just-in-time".

public class Foo extends Bazz{ 
    static int count = 0; 

    public Foo(){ 
     // Implicit call to Bazz() has side effect 
     count++; // side effect 
     Bazz.onNewFoo(this); // reference escaping 
    } 

    Bazz bazz = new Bazz(); // side effect 
    { 
     Bazz.onNewBazz(this.bazz); // reference escaping 
    } 
} 

class Bazz{ 
    static int count = 0; 

    static List<Foo> fooList = new LinkedList<>(); 
    static List<Bazz> bazzList = new LinkedList<>(); 

    static void onNewFoo(Foo foo){ 
     fooList.add(foo); 
    } 

    static void onNewBazz(Bazz bazz){ 
     bazzList.add(bazz); 
    } 

    public Bazz(){ 
     count++; 
    } 
} 

Możliwe, że powinniśmy po prostu pozwolić javac wykonać tę analizę i optymalizację. Problem polega na tym, że nie ma sposobu, aby zagwarantować, że wersja Foo(), która była w ścieżce klas podczas kompilacji, będzie taka sama jak ta, która jest w ścieżce klas w czasie wykonywania. (Co jest bardzo cenną cechą Javy - pozwala mi przenieść moją aplikację z Glassfish do Tomcat bez rekompilacji). Dlatego nie możemy ufać analizom wykonanym podczas kompilacji.

Wreszcie, uświadom sobie, że String nie różni się od Foo. Nadal będziemy musieli uruchomić tę analizę i nie ma możliwości wykonania tej analizy z wyprzedzeniem (dlatego mogę uaktualnić środowisko JRE bez ponownej kompilacji moich aplikacji).