JIT-kompilering ( engelska Just-in-Time , kompilering "exakt vid rätt tidpunkt"), dynamisk kompilering ( engelsk dynamisk översättning ) är en teknik för att öka prestandan hos mjukvarusystem som använder bytekod genom att kompilera bytekod till maskinkod eller till annat format direkt medan programmet körs. Således uppnås en hög exekveringshastighet jämfört med tolkad bytekod [1] (jämförbar med kompilerade språk) på grund av ökad minnesförbrukning (för lagring av kompileringsresultat) och kompileringstid. JIT bygger på två tidigare runtimeidéer: bytekodkompilering och dynamisk kompilering .
Eftersom JIT-kompilering i själva verket är en form av dynamisk kompilering, tillåter den användningen av teknologier som adaptiv optimering och dynamisk omkompilering . På grund av detta kan JIT-kompilering prestera bättre när det gäller prestanda än statisk kompilering. Tolkning och JIT-kompilering är särskilt väl lämpade för dynamiska programmeringsspråk , medan runtime hanterar sen typbindning och garanterar körtidssäkerhet.
Projekten LLVM , GNU Lightning [2] , libJIT (en del av DotGNU- projektet ) och RPython (en del av PyPy- projektet ) kan användas för att skapa JIT-tolkar för vilket skriptspråk som helst.
JIT-kompilering kan tillämpas både på hela programmet och på dess enskilda delar. Till exempel kan en textredigerare kompilera reguljära uttryck i farten för snabbare textsökningar. Med AOT-kompilering är detta inte möjligt för fall där data tillhandahålls under körningen av programmet, och inte vid tidpunkten för kompileringen. JIT används i implementeringar av Java (JRE), JavaScript , .NET Framework , i en av implementeringarna av Python - PyPy . [3] De befintliga vanligaste tolkarna för PHP , Ruby , Perl , Python och liknande har begränsade eller ofullständiga JITs.
De flesta JIT-implementationer har en sekventiell struktur: först kompileras applikationen till runtime virtuell maskinbytekod (AOT-kompilering), och sedan kompilerar JIT bytekoden direkt till maskinkoden. Som ett resultat slösas extra tid bort när du startar applikationen, vilket sedan kompenseras av dess snabbare drift.
I språk som Java , PHP , C# , Lua , Perl , GNU CLISP , översätts källkoden till en av de mellanliggande representationerna som kallas bytecode . Bytekod är inte maskinkoden för någon speciell processor och kan portas till olika datorarkitekturer och exekveras på exakt samma sätt. Bytekoden tolkas (exekveras) av den virtuella maskinen . JIT läser bytekod från vissa sektorer (sällan från alla på en gång) och kompilerar dem till maskinkod. Denna sektor kan vara en fil, en funktion eller vilken kod som helst. En gång kompilerad kod kan cachelagras och sedan återanvändas utan omkompilering.
En dynamiskt kompilerad miljö är en miljö där kompilatorn kan anropas av ett program under körning. Till exempel innehåller de flesta implementeringar av Common Lisp en funktion compilesom kan skapa en funktion vid körning; i Python är detta en funktion eval. Detta är bekvämt för programmeraren, eftersom han kan kontrollera vilka delar av koden som faktiskt kompileras. Det är också möjligt att kompilera dynamiskt genererad kod med denna teknik, vilket i vissa fall leder till ännu bättre prestanda än implementeringen i statiskt kompilerad kod. Det är dock värt att komma ihåg att sådana funktioner kan vara farliga, särskilt när data överförs från opålitliga källor. [fyra]
Huvudmålet med att använda JIT är att uppnå och överträffa prestandan för statisk kompilering samtidigt som man behåller fördelarna med dynamisk kompilering:
JIT är generellt sett mer effektivt än kodtolkning. Dessutom kan JIT i vissa fall visa bättre prestanda jämfört med statisk kompilering på grund av optimeringar som endast är möjliga under körning:
En typisk orsak till en fördröjning när du startar en JIT-kompilator är kostnaden för att ladda miljön och kompilera applikationen till inbyggd kod. I allmänhet gäller att ju bättre och ju fler optimeringar JIT utför, desto längre blir fördröjningen. Därför måste JIT-utvecklare hitta en kompromiss mellan kvaliteten på den genererade koden och starttiden. Det visar sig dock ofta att flaskhalsen i kompileringsprocessen inte är själva kompileringsprocessen, utan I/O-systemets förseningar (till exempel rt.jar i Java Virtual Machine (JVM) har en storlek på 40 MB , och att söka efter metadata i den tar ganska lång tid).
Ett annat optimeringsverktyg är att endast kompilera de delar av applikationen som används oftast. Detta tillvägagångssätt är implementerat i PyPy och Sun Microsystems HotSpot Java Virtual Machine .
Som en heuristik kan applikationssektionens starträkning, bytekodstorlek eller cykeldetektor användas.
Ibland är det svårt att hitta rätt kompromiss. Suns Java Virtual Machine har till exempel två driftlägen - klient och server. I klientläge är antalet kompilationer och optimeringar minimalt för snabbare start, medan i serverläge uppnås maximal prestanda, men på grund av detta ökar starttiden.
En annan teknik som kallas pre-JIT kompilerar koden innan den körs. Fördelen med denna teknik är den minskade starttiden, medan nackdelen är den dåliga kvaliteten på den kompilerade koden jämfört med runtime JIT.
Den allra första JIT-implementeringen kan tillskrivas LISP, skriven av McCarthy 1960 [5] . I sin bok Recursive functions of symbolic expressions and their computation by machine, Del I , nämner han funktioner som kompileras under körning, vilket eliminerar behovet av att mata ut kompilatorns arbete till hålkort .
En annan tidig referens till JIT kan tillskrivas Ken Thompson , som 1968 banade väg för användningen av reguljära uttryck för att söka efter understrängar i QED- textredigeraren . För att påskynda algoritmen implementerade Thompson kompilering av reguljära uttryck till IBM 7094 -maskinkod .
En metod för att få kompilerad kod föreslogs av Mitchell 1970 när han implementerade det experimentella språket LC 2 . [6] [7]
Smalltalk (1983) var en pionjär inom JIT-teknik. Översättning till ursprunglig kod utfördes på begäran och cachelagrades för senare användning. När minnet tog slut kunde systemet ta bort en del av den cachade koden från RAM och återställa den när den behövs igen. Självprogrammeringsspråket var under en tid den snabbaste implementeringen av Smalltalk och var bara dubbelt så långsamt som C , eftersom det var helt objektorienterat.
Självet övergavs av Sun, men forskningen fortsatte inom Java-språket. Termen "Just-in-time compilation" lånades från branschtermen "Just in Time" och populariserades av James Gosling , som använde termen 1993. [8] JIT används nu i nästan alla implementeringar av Java Virtual Machine .
Av stort intresse är också avhandlingen som försvarades 1994 vid ETH-universitetet (Schweiz, Zürich) av Michael Franz "Dynamisk kodgenerering - nyckeln till bärbar programvara" [9] och Juice-systemet [10] som implementerats av honom för dynamisk kodgenerering från ett bärbart semantiskt träd för Oberon- språket . Juice-systemet erbjöds som en plug-in för webbläsare.
Eftersom JIT komponerar körbar kod från data är det en fråga om säkerhet och eventuella sårbarheter.
JIT-kompilering innebär att kompilera källkod eller bytekod till maskinkod och exekvera den. Som regel skrivs resultatet till minnet och exekveras omedelbart, utan att spara på disk eller anropa det som ett separat program. I moderna arkitekturer, för att förbättra säkerheten, kan godtyckliga delar av minnet inte exekveras som maskinkod ( NX-bit ). För korrekt lansering måste minnesregioner tidigare markeras som körbara, medan för större säkerhet kan exekveringsflaggan endast ställas in efter att skrivbehörighetsflaggan har tagits bort (W^X-skyddsschema) [11] .