Jak kompilator JIT optymalizuje kod

Gdy metoda jest wybierana do kompilacji, maszyna JVM przekazuje jej kod bajtowy do kompilatora JIT (Just-In-Time). JIT musi zrozumieć semantykę i składnię kodu bajtowego, zanim będzie mógł poprawnie skompilować metodę.

Aby ułatwić kompilatorowi JIT analizę tej metody, jej kody bajtowe są najpierw ponownie sformułowane w wewnętrznej reprezentacji o nazwie trees, która przypomina kod maszynowy dokładniej niż kod bajtowy. Następnie analiza i optymalizacje są wykonywane na drzewach metody. Na końcu drzewa są tłumaczone na kod rodzimy. Pozostała część tej sekcji zawiera krótki przegląd faz kompilowania JIT. Więcej informacji na ten temat zawiera sekcja Diagnozowanie problemu z JIT lub AOT.

Kompilator JIT może używać więcej niż jednego wątku kompilacji do wykonywania zadań kompilacji JIT. Korzystanie z wielu wątków może potencjalnie pomóc aplikacjom Java szybciej uruchamiać aplikacje. W praktyce wiele wątków kompilacji JIT wykazuje poprawę wydajności tylko wtedy, gdy w systemie są nieużywane rdzenie przetwarzania.

Domyślna liczba wątków kompilacji jest identyfikowana przez wirtualną maszynę języka Java i jest zależna od konfiguracji systemu. Jeśli wynikowa liczba wątków nie jest optymalna, można przesłonić decyzję JVM za pomocą opcji -XcompilationThreads . Więcej informacji na temat korzystania z tej opcji zawiera sekcja Opcje -X.
Uwaga: Jeśli system nie ma nieużywanych rdzeni przetwarzania, zwiększenie liczby wątków kompilacji jest mało prawdopodobne, aby uzyskać poprawę wydajności.

Kompilacja składa się z następujących faz. Wszystkie fazy z wyjątkiem generowania kodu rodzimego to kod wieloplatformowy.

Faza 1-inokacja

Wkładka jest procesem, przez który drzewa mniejszych metod są łączone, lub "wstawiane", do drzew ich dzwoniących. Przyspiesza to często wykonywane wywołania metod. Stosowane są dwa algorytmy inokowania o różnym poziomie agresywności, w zależności od aktualnego poziomu optymalizacji. Optymalizacje wykonane w tej fazie obejmują:
  • Trivial inlining
  • Inkreślenie wykresu wywołania
  • Eliminacja rekurencji tylnej
  • Optymalizacje ochrony przed zgłosami wirtualnymi

Faza 2-optymalizacje lokalne

Lokalne optymalizacje analizują i poprawiają małą sekcję kodu w danym momencie. Wiele lokalnych optymalizacje wdraża sprawdzone i sprawdzone techniki stosowane w klasycznych statycznych kompilatorach. Optymalizacje obejmują:
  • Lokalne analizy przepływu danych i optymalizacje
  • Rejestrowanie optymalizacji wykorzystania
  • Uproszczenia idiomów Java
Techniki te są stosowane wielokrotnie, zwłaszcza po globalnych optymalizacji, które mogły zwrócić uwagę na więcej możliwości poprawy.

Faza 3-optymalizacje przepływu sterowania

Optymalizacje przepływu sterowania analizują przepływ sterowania wewnątrz metody (lub konkretnych jego sekcji) i przearanżują ścieżki kodu w celu zwiększenia ich efektywności. Optymalizacje to:
  • Zmiana kolejności, podział i usuwanie kodu
  • Redukcja pętli i inwersja
  • Pętla strugająca i pętla-nieinwazyjny ruch kodu
  • Pętla unrolling i peeling
  • Kontrola wersji pętli i specjalizacja
  • Optymalizacja ukierunkowanych wyjątków
  • Analiza przełączników

Faza 4-optymalizacje globalne

Globalnie optymalizacje pracują nad całą metodą na raz. Są one bardziej "drogie", wymagające większych ilości czasu kompilacji, ale mogą zapewnić wielki wzrost wydajności. Optymalizacje to:
  • Globalne analizy przepływu danych i optymalizacje
  • Częściowa korekta nadmiarowości
  • Analiza zmiany znaczenia
  • Optymalizacje czyszczenia pamięci i przydzielania pamięci
  • Optymalizacje synchronizacji

Faza 5-generowanie kodu rodzimego

Procesy generowania kodu rodzimego różnią się w zależności od architektury platformy. Ogólnie, podczas tej fazy kompilacji drzewa metody są tłumaczone na instrukcje kodu maszynowego; niektóre małe optymalizacje są wykonywane zgodnie z charakterystyką architektury. Skompilowany kod jest umieszczany w części obszaru procesu maszyny JVM o nazwie pamięć podręczna kodu. Położenie metody w pamięci podręcznej kodu jest rejestrowane, tak więc przyszłe wywołania do niej będą wywoływane skompilowany kod. W danym momencie proces JVM składa się z plików wykonywalnych JVM i zestawu skompilowanego kodu JIT, który jest dynamicznie dowiązany do interpretera kodu bajtowego w maszynie JVM.