Pamiętaj, że poniższe zestawienie porównuje jedynie różnicę między kompilacją natywną a kompilacją JIT i nie obejmuje specyfiki konkretnego języka lub frameworka. Mogą istnieć uzasadnione powody, aby wybrać konkretną platformę poza tym.
Gdy twierdzimy, że kod macierzysty jest szybszy, mówimy o typowym przypadku użycia kodu skompilowanego w sposób natywny w porównaniu do kodu skompilowanego w JIT, w którym typowe użycie aplikacji skompilowanej w JIT ma być uruchamiane przez użytkownika, z natychmiastowymi rezultatami (np. Brak najpierw czeka na kompilatorze). W takim przypadku nie sądzę, aby ktokolwiek mógł z prostą miną twierdzić, że skompilowany kod JIT może dopasować lub pokonać kod natywny.
Załóżmy, że mamy program napisany w jakimś języku X i możemy go skompilować za pomocą natywnego kompilatora i ponownie za pomocą kompilatora JIT. Każdy przepływ pracy obejmuje te same etapy, które można uogólnić jako (Kod -> Przedstawiciel pośredni -> Kod maszynowy -> Wykonanie). Duża różnica między dwoma to, które etapy widzi użytkownik, a które programista. W przypadku kompilacji natywnej programista widzi wszystko oprócz etapu wykonania, ale w przypadku rozwiązania JIT kompilacja do kodu maszynowego jest postrzegana przez użytkownika, oprócz wykonywania.
Twierdzenie, że A jest szybsze niż B, odnosi się do czasu potrzebnego na uruchomienie programu, jak widzi użytkownik . Jeśli założymy, że oba fragmenty kodu działają identycznie na etapie wykonywania, musimy założyć, że przepływ pracy JIT jest wolniejszy dla użytkownika, ponieważ musi on także zobaczyć czas T kompilacji do kodu maszynowego, gdzie T> 0. Tak , aby każda możliwość, aby przepływ pracy JIT działał tak samo jak natywny przepływ pracy, dla użytkownika, musimy skrócić czas wykonywania kodu, tak aby wykonanie + kompilacja do kodu maszynowego były niższe niż tylko etap wykonania natywnego przepływu pracy. Oznacza to, że musimy lepiej zoptymalizować kod w kompilacji JIT niż w kompilacji natywnej.
Jest to jednak raczej niewykonalne, ponieważ aby wykonać niezbędne optymalizacje w celu przyspieszenia wykonywania, musimy poświęcić więcej czasu na kompilację do etapu kodu maszynowego, a zatem za każdym razem, gdy zaoszczędzimy w wyniku zoptymalizowania kodu, zostanie utracony, ponieważ dodajemy go do kompilacji. Innymi słowy, „powolność” rozwiązania opartego na JIT nie wynika wyłącznie z dodatkowego czasu na kompilację JIT, ale kod wygenerowany przez tę kompilację działa wolniej niż rozwiązanie rodzime.
Posłużę się przykładem: Zarejestruj przydział. Ponieważ dostęp do pamięci jest kilka tysięcy razy wolniejszy niż dostęp do rejestrów, najlepiej, gdy jest to możliwe, chcemy korzystać z rejestrów i mieć jak najmniej dostępów do pamięci, ale mamy ograniczoną liczbę rejestrów i musimy przelać stan do pamięci, gdy jest to potrzebne rejestr. Jeśli użyjemy algorytmu alokacji rejestru, którego obliczenie zajmuje 200 ms, w wyniku czego zaoszczędzimy 2 ms czasu wykonania - nie wykorzystujemy najlepiej czasu kompilatora JIT. Rozwiązania takie jak algorytm Chaitin, który może generować wysoce zoptymalizowany kod, są nieodpowiednie.
Rolą kompilatora JIT jest jednak zachowanie najlepszej równowagi między czasem kompilacji a jakością produkowanego kodu, z dużym naciskiem na szybki czas kompilacji, ponieważ nie chcesz, aby użytkownik czekał. Wydajność wykonywanego kodu jest mniejsza w przypadku JIT, ponieważ natywny kompilator nie jest związany (dużo) czasem w optymalizowaniu kodu, więc można swobodnie korzystać z najlepszych algorytmów. Możliwość, że ogólna kompilacja + wykonanie kompilatora JIT może pobić tylko czas wykonania natywnie skompilowanego kodu, wynosi 0.
Ale nasze maszyny wirtualne nie ograniczają się tylko do kompilacji JIT. Korzystają z technik kompilacji Ahead-of-time, buforowania, wymiany na gorąco i optymalizacji adaptacyjnych. Zmodyfikujmy więc nasze twierdzenie, że wydajność jest tym, co widzi użytkownik, i ograniczmy ją do czasu potrzebnego na wykonanie programu (załóżmy, że skompilowaliśmy AOT). Możemy skutecznie sprawić, aby kod wykonawczy był równoważny natywnemu kompilatorowi (a może lepiej?). Wielkim twierdzeniem dla maszyn wirtualnych jest to, że mogą one być w stanie wytworzyć kod lepszej jakości niż natywny kompilator, ponieważ ma on dostęp do większej ilości informacji - informacji o uruchomionym procesie, takich jak częstotliwość wykonywania określonej funkcji. Maszyna wirtualna może następnie zastosować adaptacyjne optymalizacje do najbardziej niezbędnego kodu za pomocą wymiany na gorąco.
Jest jednak problem z tym argumentem - zakłada on, że optymalizacja sterowana profilem i tym podobne jest czymś wyjątkowym dla maszyn wirtualnych, co nie jest prawdą. Możemy zastosować go również do kompilacji natywnej - kompilując naszą aplikację z włączonym profilowaniem, rejestrując informacje, a następnie ponownie kompilując aplikację z tym profilem. Prawdopodobnie warto również zauważyć, że wymiana kodu na gorąco nie jest czymś, co może zrobić tylko kompilator JIT, możemy to zrobić dla kodu natywnego - chociaż rozwiązania oparte na JIT są łatwiej dostępne i znacznie łatwiejsze dla programisty. Główne pytanie brzmi zatem: czy maszyna wirtualna może nam dostarczyć informacji, których natywna kompilacja nie może uzyskać, co może zwiększyć wydajność naszego kodu?
Sam tego nie widzę. Możemy zastosować większość technik typowej maszyny wirtualnej również do kodu natywnego - chociaż proces jest bardziej zaangażowany. Podobnie możemy zastosować wszelkie optymalizacje natywnego kompilatora z powrotem do maszyny wirtualnej, która korzysta z kompilacji AOT lub optymalizacji adaptacyjnych. Rzeczywistość jest taka, że różnica między rodzimym kodem uruchomionym a maszyną wirtualną nie jest tak duża, jak nam się wydaje. Ostatecznie prowadzą do tego samego rezultatu, ale przyjmują inne podejście, aby się tam dostać. Maszyna wirtualna używa iteracyjnego podejścia do tworzenia zoptymalizowanego kodu, w którym natywny kompilator oczekuje go od samego początku (i można go ulepszyć za pomocą iteracyjnego podejścia).
Programista C ++ może argumentować, że potrzebuje optymalizacji od samego początku i nie powinien czekać na maszynę wirtualną, aby dowiedzieć się, jak to zrobić, jeśli w ogóle. Jest to prawdopodobnie ważny punkt w naszej obecnej technologii, ponieważ obecny poziom optymalizacji w naszych maszynach wirtualnych jest gorszy od tego, co oferują natywne kompilatory - ale nie zawsze tak będzie, jeśli rozwiązania AOT w naszych maszynach wirtualnych poprawią się itp.