Używam następującego kodu Java na laptopie z procesorem Intel Core i7 2,7 GHz. Zamierzałem pozwolić mu zmierzyć, ile czasu zajmie zakończenie pętli z 2 ^ 32 iteracjami, które, jak spodziewałem się, trwały około 1,48 sekundy (4 / 2,7 = 1,48).
Ale tak naprawdę zajmuje to tylko 2 milisekundy zamiast 1,48 s. Zastanawiam się, czy jest to wynikiem jakiejkolwiek optymalizacji JVM pod spodem?
public static void main(String[] args)
{
long start = System.nanoTime();
for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++){
}
long finish = System.nanoTime();
long d = (finish - start) / 1000000;
System.out.println("Used " + d);
}
javap -v
aby zobaczyć.javac
robi bardzo niewiele rzeczywistej optymalizacji i pozostawia większość z niej kompilatorowi JIT.Odpowiedzi:
Zachodzi tutaj jedna z dwóch możliwości:
Kompilator zdał sobie sprawę, że pętla jest zbędna i nic nie robi, więc zoptymalizował ją.
JIT (kompilator just-in-time) zdał sobie sprawę, że pętla jest nadmiarowa i nic nie robi, więc zoptymalizował ją.
Nowoczesne kompilatory są bardzo inteligentne; mogą zobaczyć, kiedy kod jest bezużyteczny. Spróbuj wstawić pustą pętlę do GodBolt i spójrz na wyjście, a następnie włącz
-O2
optymalizacje, zobaczysz, że wynik jest podobny doChciałbym coś wyjaśnić, w Javie większość optymalizacji jest wykonywana przez JIT. W niektórych innych językach (np. C / C ++) większość optymalizacji jest wykonywana przez pierwszy kompilator.
źródło
Wygląda na to, że został zoptymalizowany przez kompilator JIT. Kiedy go wyłączam (
-Djava.compiler=NONE
), kod działa znacznie wolniej:Umieściłem kod OP w
class MyClass
.źródło
Powiem tylko to, co oczywiste - że jest to optymalizacja JVM, która ma miejsce, pętla zostanie po prostu w ogóle usunięta. Oto mały test, który pokazuje ogromną różnicę
JIT
, gdy jest włączony / włączony tylko dlaC1 Compiler
iw ogóle wyłączony.Zastrzeżenie: nie pisz takich testów - to tylko po to, aby udowodnić, że faktyczne „usuwanie” pętli ma miejsce w
C2 Compiler
:Wyniki pokazują, że w zależności od tego, która część
JIT
jest włączona, metoda przyspiesza (o wiele szybciej, że wygląda na to, że "nic nie robi" - usuwanie pętli, co wydaje się mieć miejsce wC2 Compiler
- co jest maksymalnym poziomem):źródło
Jak już wspomniano, kompilator JIT (just-in-time) może zoptymalizować pustą pętlę, aby usunąć niepotrzebne iteracje. Ale jak?
W rzeczywistości istnieją dwa kompilatory JIT: C1 i C2 . Najpierw kod jest kompilowany za pomocą C1. C1 zbiera statystyki i pomaga JVM odkryć, że w 100% przypadków nasza pusta pętla niczego nie zmienia i jest bezużyteczna. W tej sytuacji na scenę wkracza C2. Gdy kod jest wywoływany bardzo często, można go zoptymalizować i skompilować za pomocą C2 przy użyciu zebranych statystyk.
Jako przykład przetestuję następny fragment kodu (mój JDK jest ustawiony na wersję 9-wewnętrzną slowdebug ):
Z następującymi opcjami wiersza poleceń:
Istnieją różne wersje mojej metody uruchamiania , skompilowane odpowiednio z C1 i C2. U mnie ostateczny wariant (C2) wygląda mniej więcej tak:
Jest trochę brudny, ale jeśli przyjrzysz się uważnie, możesz zauważyć, że nie ma tu długiej pętli. Istnieją 3 bloki: B1, B2 i B3, a etapy wykonania mogą być
B1 -> B2 -> B3
lubB1 -> B3
. GdzieFreq: 1
- znormalizowana szacowana częstotliwość wykonywania bloku.źródło
Mierzysz czas potrzebny na wykrycie, że pętla nic nie robi, kompilujesz kod w wątku w tle i eliminujesz kod.
Jeśli uruchomisz to z
-XX:+PrintCompilation
, zobaczysz, że kod został skompilowany w tle do kompilatora poziomu 3 lub C1 i po kilku pętlach do poziomu 4 z C4.Jeśli zmienisz pętlę, aby używała a
long
, nie będzie ona tak zoptymalizowana.zamiast tego dostajesz
źródło
long
licznik miałby zapobiegać tej samej optymalizacji?int
note char i short są faktycznie takie same na poziomie kodu bajtów.Bierzesz pod uwagę czas rozpoczęcia i zakończenia w nanosekundach i dzielisz przez 10 ^ 6, aby obliczyć opóźnienie
powinno być,
10^9
ponieważ1
sekunda =10^9
nanosekunda.źródło