Czym różni się kompilator JIT od zwykłego kompilatora?
22
Wiele komplikuje się w kompilatorach JIT dla języków takich jak Java, Ruby i Python. Czym różnią się kompilatory JIT od kompilatorów C / C ++ i dlaczego kompilatory napisane dla Java, Ruby lub Python nazywane są kompilatorami JIT, podczas gdy kompilatory C / C ++ to tylko kompilatory?
Kompilatory JIT kompilują kod w locie, tuż przed ich wykonaniem, a nawet wtedy, gdy są już wykonywane. W ten sposób maszyna wirtualna, w której działa kod, może sprawdzać wzorce w wykonywaniu kodu, aby umożliwić optymalizacje, które byłyby możliwe tylko z informacjami w czasie wykonywania. Ponadto, jeśli maszyna wirtualna zdecyduje, że skompilowana wersja nie jest wystarczająco dobra z jakiegokolwiek powodu (np. Zbyt wiele braków pamięci podręcznej lub kod często zgłaszający wyjątek), może zdecydować o ponownej kompilacji w inny sposób, co prowadzi do znacznie mądrzejszego kompilacja.
Z drugiej strony, kompilatory C i C ++ tradycyjnie nie są JIT. Kompilują się w jednym ujęciu tylko raz na maszynie programisty, a następnie tworzony jest plik wykonywalny.
Czy istnieją kompilatory JIT, które śledzą błędy w pamięci podręcznej i odpowiednio dostosowują swoją strategię kompilacji? @Victor
Martin Berger
@MartinBerger Nie wiem, czy robią to dostępne kompilatory JIT, ale dlaczego nie? W miarę jak komputery stają się mocniejsze i rozwija się informatyka, ludzie mogą zastosować więcej optymalizacji programu. Na przykład, kiedy narodziła się Java, jest bardzo powolna, może 20 razy w porównaniu do skompilowanych, ale teraz wydajność nie jest tak opóźniona i może być porównywalna z niektórymi kompilatorami
phuclv
Słyszałem o tym na blogu dawno temu. Kompilator może kompilować się inaczej w zależności od bieżącego procesora. Na przykład może automatycznie wektoryzować część kodu, jeśli wykryje, że procesor obsługuje SSE / AVX. Gdy dostępne są nowsze rozszerzenia SIMD, mogą się kompilować z nowszymi, więc programista nie musi niczego zmieniać. Lub jeśli może zorganizować operacje / dane, aby skorzystać z większej pamięci podręcznej lub zmniejszyć brak pamięci podręcznej
phuclv
@ LưuVĩnhPhúc Chào! Cieszę się, że w to wierzę, ale różnica polega na tym, że np. Typ procesora lub rozmiar pamięci podręcznej są czymś statycznym, który nie zmienia się podczas obliczeń, więc może to łatwo zrobić również kompilator statyczny. Pamięć podręczna brakuje OTOH są bardzo dynamiczne.
Martin Berger,
12
JIT jest skrótem od kompilatora just-in-time, a nazwa to misson: w czasie wykonywania określa wartościowe optymalizacje kodu i stosuje je. Nie zastępuje zwykłych kompilatorów, ale jest częścią tłumaczy. Zauważ, że języki takie jak Java, które używają kodu pośredniego, mają zarówno normalny kompilator do tłumaczenia kodu źródłowego na pośredni, jak i JIT zawarte w interpreterie w celu zwiększenia wydajności.
Optymalizacje kodu z pewnością mogą być wykonywane przez „klasyczne” kompilatory, ale zwróć uwagę na główną różnicę: kompilatory JIT mają dostęp do danych w czasie wykonywania. To ogromna zaleta; prawidłowe wykorzystanie może być oczywiście trudne.
Rozważmy na przykład taki kod:
m(a : String, b : String, k : Int) {
val c : Int;
switch (k) {
case 0 : { c = 7; break; }
...
case 17 : { c = complicatedMethod(k, a+b); break; }
}
return a.length + b.length - c + 2*k;
}
Normalny kompilator nie może z tym zrobić zbyt wiele. Jednak kompilator JIT może wykryć, że mjest wywoływany tylko z k==0jakiegoś powodu (takie rzeczy mogą się zdarzyć, gdy kod zmienia się w czasie); może następnie utworzyć mniejszą wersję kodu (i skompilować go do kodu natywnego, chociaż uważam, że to drobna kwestia, koncepcyjnie):
W tym momencie prawdopodobnie nawet wstawi wywołanie metody, ponieważ jest to teraz trywialne.
Najwyraźniej Słońce odrzuciło większość optymalizacji wykonanych javacw Javie 6; Powiedziano mi, że te optymalizacje utrudniały JIT wiele, a naiwnie skompilowany kod działał szybciej. Domyśl.
Nawiasem mówiąc, w obecności usuwania typu (np. Generics w Javie) JIT jest w rzeczywistości wadą typów wrt.
Raphael
Środowisko wykonawcze jest więc bardziej skomplikowane niż język skompilowany, ponieważ środowisko wykonawcze musi gromadzić dane w celu optymalizacji.
saadtaame
2
W wielu przypadkach JIT nie byłby w stanie zastąpić mwersją, która nie sprawdziła sięk ponieważ nie byłby w stanie udowodnić, mże nigdy nie zostanie wywołany z niezerową wartością k, ale nawet bez możliwości udowodnienia, że mógłby zastąpić to z static miss_count; if (k==0) return a.length+b.length-7; else if (miss_count++ < 16) { ... unoptimized code for m ...} else { ... consider other optimizations...}.
supercat
1
Zgadzam się z @supercat i uważam, że twój przykład jest dość mylący. Tylko, jeżelik=0 zawsze , co oznacza, że nie jest to funkcja danych wejściowych ani środowiska, można bezpiecznie zrezygnować z testu - ale określenie tego wymaga analizy statycznej, która jest bardzo kosztowna, a zatem dostępna tylko w czasie kompilacji. JIT może wygrać, gdy jedna ścieżka przez blok kodu jest pobierana znacznie częściej niż inne, a wersja kodu wyspecjalizowana dla tej ścieżki byłaby znacznie szybsza. Ale JIT nadal będzie emitował test, aby sprawdzić, czy obowiązuje szybka ścieżka , a jeśli nie, wybierz „wolną ścieżkę”.
j_random_hacker
Przykład: funkcja wymaga Base* p parametr i wywołuje za jego pośrednictwem funkcje wirtualne; Analiza środowiska wykonawczego pokazuje, że rzeczywisty obiekt wskazywany zawsze (lub prawie zawsze) wydaje się być Derived1typu. JIT może wygenerować nową wersję funkcji ze statycznie rozwiązanymi (lub nawet wbudowanymi) wywołaniami Derived1metod. Ten kod byłby poprzedzony warunkiem, który sprawdza, czy pwskaźnik vtable wskazuje na oczekiwaną Derived1tabelę; jeśli nie, przeskakuje do oryginalnej wersji funkcji z wolniejszymi dynamicznie rozwiązywanymi wywołaniami metod.
JIT jest skrótem od kompilatora just-in-time, a nazwa to misson: w czasie wykonywania określa wartościowe optymalizacje kodu i stosuje je. Nie zastępuje zwykłych kompilatorów, ale jest częścią tłumaczy. Zauważ, że języki takie jak Java, które używają kodu pośredniego, mają zarówno normalny kompilator do tłumaczenia kodu źródłowego na pośredni, jak i JIT zawarte w interpreterie w celu zwiększenia wydajności.
Optymalizacje kodu z pewnością mogą być wykonywane przez „klasyczne” kompilatory, ale zwróć uwagę na główną różnicę: kompilatory JIT mają dostęp do danych w czasie wykonywania. To ogromna zaleta; prawidłowe wykorzystanie może być oczywiście trudne.
Rozważmy na przykład taki kod:
Normalny kompilator nie może z tym zrobić zbyt wiele. Jednak kompilator JIT może wykryć, że
m
jest wywoływany tylko zk==0
jakiegoś powodu (takie rzeczy mogą się zdarzyć, gdy kod zmienia się w czasie); może następnie utworzyć mniejszą wersję kodu (i skompilować go do kodu natywnego, chociaż uważam, że to drobna kwestia, koncepcyjnie):W tym momencie prawdopodobnie nawet wstawi wywołanie metody, ponieważ jest to teraz trywialne.
Najwyraźniej Słońce odrzuciło większość optymalizacji wykonanych
javac
w Javie 6; Powiedziano mi, że te optymalizacje utrudniały JIT wiele, a naiwnie skompilowany kod działał szybciej. Domyśl.źródło
m
wersją, która nie sprawdziła sięk
ponieważ nie byłby w stanie udowodnić,m
że nigdy nie zostanie wywołany z niezerową wartościąk
, ale nawet bez możliwości udowodnienia, że mógłby zastąpić to zstatic miss_count; if (k==0) return a.length+b.length-7; else if (miss_count++ < 16) { ... unoptimized code for
m...} else { ... consider other optimizations...}
.k=0
zawsze , co oznacza, że nie jest to funkcja danych wejściowych ani środowiska, można bezpiecznie zrezygnować z testu - ale określenie tego wymaga analizy statycznej, która jest bardzo kosztowna, a zatem dostępna tylko w czasie kompilacji. JIT może wygrać, gdy jedna ścieżka przez blok kodu jest pobierana znacznie częściej niż inne, a wersja kodu wyspecjalizowana dla tej ścieżki byłaby znacznie szybsza. Ale JIT nadal będzie emitował test, aby sprawdzić, czy obowiązuje szybka ścieżka , a jeśli nie, wybierz „wolną ścieżkę”.Base* p
parametr i wywołuje za jego pośrednictwem funkcje wirtualne; Analiza środowiska wykonawczego pokazuje, że rzeczywisty obiekt wskazywany zawsze (lub prawie zawsze) wydaje się byćDerived1
typu. JIT może wygenerować nową wersję funkcji ze statycznie rozwiązanymi (lub nawet wbudowanymi) wywołaniamiDerived1
metod. Ten kod byłby poprzedzony warunkiem, który sprawdza, czyp
wskaźnik vtable wskazuje na oczekiwanąDerived1
tabelę; jeśli nie, przeskakuje do oryginalnej wersji funkcji z wolniejszymi dynamicznie rozwiązywanymi wywołaniami metod.