JIT kompilace ( anglicky Just-in-Time , kompilace „přesně v pravý čas“), dynamická kompilace ( anglický dynamický překlad ) je technologie pro zvýšení výkonu softwarových systémů využívajících bytecode kompilací bajtového kódu do strojového kódu nebo přímo do jiného formátu. zatímco program běží. Je tak dosaženo vysoké rychlosti provádění ve srovnání s interpretovaným bajtovým kódem [1] (srovnatelné s kompilovanými jazyky) díky zvýšené spotřebě paměti (pro ukládání výsledků kompilace) a době kompilace. JIT staví na dvou dřívějších nápadech za běhu: kompilaci bajtkódu a dynamickou kompilaci .
Protože kompilace JIT je ve skutečnosti formou dynamické kompilace, umožňuje použití technologií, jako je adaptivní optimalizace a dynamická rekompilace . Z tohoto důvodu může kompilace JIT fungovat lépe z hlediska výkonu než statická kompilace. Interpretace a kompilace JIT jsou zvláště vhodné pro dynamické programovací jazyky , zatímco běhové prostředí zpracovává vazbu pozdního typu a zaručuje bezpečnost běhu.
Projekty LLVM , GNU Lightning [2] , libJIT (součást projektu DotGNU ) a RPython (součást projektu PyPy ) lze použít k vytvoření interpretů JIT pro jakýkoli skriptovací jazyk.
Kompilaci JIT lze aplikovat jak na celý program, tak i na jeho jednotlivé části. Například textový editor může za běhu kompilovat regulární výrazy pro rychlejší vyhledávání textu. S kompilací AOT to není možné v případech, kdy jsou data poskytnuta během provádění programu, a nikoli v době kompilace. JIT se používá v implementacích Java (JRE), JavaScriptu , .NET Frameworku , v jedné z implementací Pythonu - PyPy . [3] Stávající nejběžnější interpreti pro PHP , Ruby , Perl , Python a podobně mají omezené nebo neúplné JIT.
Většina implementací JIT má sekvenční strukturu: nejprve je aplikace zkompilována do bytekódu virtuálního stroje za běhu (AOT kompilace) a poté JIT zkompiluje bajtový kód přímo do strojového kódu. Výsledkem je ztráta času navíc při spouštění aplikace, což je následně kompenzováno jejím rychlejším chodem.
V jazycích jako Java , PHP , C# , Lua , Perl , GNU CLISP je zdrojový kód přeložen do jedné ze středních reprezentací nazývaných bytecode . Bytecode není strojový kód žádného konkrétního procesoru a lze jej přenést na různé počítačové architektury a spustit přesně stejným způsobem. Bytový kód je interpretován (spouštěn) virtuálním strojem . JIT čte bajtkód z některých sektorů (zřídka ze všech najednou) a kompiluje je do strojového kódu. Tímto sektorem může být soubor, funkce nebo jakýkoli kus kódu. Zkompilovaný kód lze uložit do mezipaměti a poté znovu použít bez rekompilace.
Dynamicky kompilované prostředí je prostředí, ve kterém může být kompilátor volán aplikací za běhu. Například většina implementací Common Lisp obsahuje funkci compile, která může vytvořit funkci za běhu; v Pythonu je to funkce eval. To je výhodné pro programátora, protože může kontrolovat, které části kódu jsou skutečně kompilovány. Pomocí této techniky je také možné zkompilovat dynamicky generovaný kód, což v některých případech vede k ještě lepšímu výkonu než implementace ve staticky kompilovaném kódu. Je však třeba si uvědomit, že takové funkce mohou být nebezpečné, zejména pokud jsou data přenášena z nedůvěryhodných zdrojů. [čtyři]
Hlavním cílem použití JIT je dosáhnout a překročit výkon statické kompilace při zachování výhod dynamické kompilace:
JIT je obecně efektivnější než interpretace kódu. Navíc v některých případech může JIT vykazovat lepší výkon ve srovnání se statickou kompilací díky optimalizacím, které jsou možné pouze za běhu:
Typickým důvodem prodlevy při spouštění JIT kompilátoru jsou náklady na načítání prostředí a kompilaci aplikace do nativního kódu. Obecně platí, že čím lépe a čím více optimalizací JIT provede, tím delší bude zpoždění. Vývojáři JIT proto musí najít kompromis mezi kvalitou generovaného kódu a dobou spuštění. Často se však ukazuje, že úzkým hrdlem v procesu kompilace není samotný proces kompilace, ale zpoždění I/O systému (například rt.jar v Java Virtual Machine (JVM) má velikost 40 MB a hledání metadat v něm zabere poměrně hodně času).
Dalším optimalizačním nástrojem je kompilace pouze těch částí aplikace, které se používají nejčastěji. Tento přístup je implementován v PyPy a Sun Microsystems HotSpot Java Virtual Machine .
Jako heuristiku lze použít počet spuštění části aplikace, velikost bajtkódu nebo detektor cyklu.
Někdy je těžké najít ten správný kompromis. Například Java Virtual Machine společnosti Sun má dva provozní režimy – klient a server. V klientském režimu je počet kompilací a optimalizací minimální pro rychlejší spouštění, zatímco v serverovém režimu je dosaženo maximálního výkonu, ale kvůli tomu se prodlužuje doba spouštění.
Další technika zvaná pre-JIT zkompiluje kód před jeho spuštěním. Výhodou této techniky je zkrácená doba spouštění, nevýhodou naopak špatná kvalita zkompilovaného kódu oproti runtime JIT.
Úplně první implementaci JIT lze připsat LISP, který napsal McCarthy v roce 1960 [5] . Ve své knize Rekurzivní funkce symbolických výrazů a jejich strojový výpočet, část I , zmiňuje funkce, které se kompilují za běhu, čímž odpadá nutnost vypisovat práci kompilátoru na děrné štítky .
Další ranou zmínku o JIT lze připsat Kenu Thompsonovi , který v roce 1968 propagoval použití regulárních výrazů pro hledání podřetězců v textovém editoru QED . Pro urychlení algoritmu Thompson implementoval kompilaci regulárních výrazů do strojového kódu IBM 7094 .
Metodu pro získání zkompilovaného kódu navrhl Mitchell v roce 1970 , když implementoval experimentální jazyk LC 2 . [6] [7]
Smalltalk (1983) byl průkopníkem v technologii JIT. Překlad do nativního kódu byl proveden na vyžádání a uložen do mezipaměti pro pozdější použití. Když došla paměť, systém mohl odstranit část kódu uloženého v mezipaměti z paměti RAM a obnovit jej, když bude znovu potřeba. Programovací jazyk Self byl na nějakou dobu nejrychlejší implementací Smalltalku a byl pouze dvakrát pomalejší než C , protože byl zcela objektově orientovaný.
Self bylo opuštěno Sunem, ale výzkum pokračoval v jazyce Java. Termín „kompilace Just-in-time“ byl vypůjčen z průmyslového termínu „Just in Time“ a popularizován Jamesem Goslingem , který tento termín použil v roce 1993. [8] JIT se nyní používá téměř ve všech implementacích Java Virtual Machine. .
Velkou zajímavostí je také práce obhájená v roce 1994 na ETH University (Švýcarsko, Curych) Michaelem Franzem „Dynamické generování kódu – klíč k přenosnému softwaru“ [9] a jím implementovaný systém Juice [10] pro dynamické generování kódu z přenosného sémantického stromu pro jazyk Oberon . Systém Juice byl nabízen jako zásuvný modul pro internetové prohlížeče.
Vzhledem k tomu, že JIT skládá spustitelný kód z dat, je zde otázka bezpečnosti a možných zranitelností.
Kompilace JIT zahrnuje kompilaci zdrojového kódu nebo bajtkódu do strojového kódu a jeho spuštění. Výsledek je zpravidla zapsán do paměti a proveden okamžitě, bez mezilehlého ukládání na disk nebo jeho volání jako samostatného programu. V moderních architekturách nelze pro zlepšení zabezpečení provádět libovolné části paměti jako strojový kód ( bit NX ). Pro správné spuštění musí být oblasti paměti předem označeny jako spustitelné, zatímco pro větší bezpečnost lze příznak spuštění nastavit až po odstranění příznaku oprávnění k zápisu (schéma ochrany W^X) [11] .