JIT-sammanställning

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.

Implementeringsfunktioner

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.

Beskrivning

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:

  1. Kompilering kan göras direkt för målprocessorn och operativsystemet som programmet körs på. Till exempel kan JIT använda vektorns SSE2- processortillägg om den upptäcker stöd för dem.
  2. Miljön kan samla in statistik om det pågående programmet och göra optimeringar utifrån denna information. Vissa statiska kompilatorer kan också ta information om tidigare körningar av applikationen som indata.
  3. Miljön kan göra globala kodoptimeringar (som att infoga biblioteksfunktioner i kod) utan att förlora fördelarna med dynamisk kompilering och utan överkostnaderna med statiska kompilatorer och länkare .
  4. Enklare kodombyggnad för bättre cacheanvändning .

Startfördröjning, åtgärder

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.

Historik

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.

Säkerhet

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] .

Se även

Anteckningar

  1. Core Java: An Integrated Approach Arkiverad 27 augusti 2017 på Wayback Machine , 2008, ISBN 9788177228366 , Dreamtech Press, 2008. s.12
  2. GNU lightning - GNU Project - Free Software Foundation (FSF) . Hämtad 27 augusti 2017. Arkiverad från originalet 19 september 2017.
  3. Benjamin Peterson - PyPy Arkiverad 12 maj 2008 på Wayback Machine
  4. Och igen om faran med eval () Arkiverad kopia av 13 september 2014 på Wayback Machine , habrahabr
  5. Aycock 2003, 2. JIT Compilation Techniques, 2.1 Genesis, sid. 98.
  6. Aycock 2003, 2. JIT Compilation Techniques, 2.2 LC², sid. 98-99.
  7. Mitchell, JG (1970). Design och konstruktion av flexibla och effektiva interaktiva programmeringssystem .
  8. Aycock & 2003 2.14 Java, sid. 107, fotnot 13.
  9. Michael Franz - OberonCore Arkiverad 26 september 2017 på Wayback Machine ; avhandling, med titeln "Code Generation On-The-Fly: A Key To Portable Software," Arkiverad 7 september 2017 på Wayback Machine
  10. Juice - OberonCore . Hämtad 7 november 2009. Arkiverad från originalet 23 december 2009.
  11. Skriv XOR Execute JIT Support Lands For Mozilla Firefox Arkiverad 2 augusti 2017 på Wayback Machine / Phoronix, 4 januari   2016