Compilarea JIT ( în engleză Just-in-Time , compilarea „exact la momentul potrivit”), compilarea dinamică ( traducere dinamică în engleză ) este o tehnologie pentru creșterea performanței sistemelor software care utilizează bytecode prin compilarea bytecode în codul mașină sau în alt format direct în timp ce programul rulează. Astfel, se realizează o viteză mare de execuție în comparație cu bytecode interpretat [1] (comparabil cu limbajele compilate) datorită consumului crescut de memorie (pentru stocarea rezultatelor compilației) și timpului de compilare. JIT se bazează pe două idei anterioare despre mediul de rulare:compilare bytecode și compilare dinamică .
Deoarece compilarea JIT este, de fapt, o formă de compilare dinamică, permite utilizarea tehnologiilor precum optimizarea adaptivă și recompilarea dinamică . Din acest motiv, compilarea JIT poate funcționa mai bine în ceea ce privește performanța decât compilarea statică. Interpretarea și compilarea JIT sunt deosebit de potrivite pentru limbajele de programare dinamice , în timp ce runtime-ul se ocupă de legarea de tip târziu și garantează siguranța de rulare.
Proiectele LLVM , GNU Lightning [2] , libJIT (parte a proiectului DotGNU ) și RPython (parte a proiectului PyPy ) pot fi utilizate pentru a crea interpreți JIT pentru orice limbaj de scripting.
Compilarea JIT poate fi aplicată atât întregului program, cât și părților sale individuale. De exemplu, un editor de text poate compila expresii regulate din mers pentru căutări mai rapide de text. Cu compilarea AOT, acest lucru nu este posibil pentru cazurile în care datele sunt furnizate în timpul execuției programului și nu în momentul compilării. JIT este folosit în implementările Java (JRE), JavaScript , .NET Framework , într-una dintre implementările Python - PyPy . [3] Cei mai obișnuiți interpreți existenți pentru PHP , Ruby , Perl , Python și altele asemenea au JIT-uri limitate sau incomplete.
Majoritatea implementărilor JIT au o structură secvențială: mai întâi, aplicația este compilată în codul de octeți al mașinii virtuale de rulare (compilare AOT), iar apoi JIT compilează codul de octeți direct în codul mașinii. Ca urmare, se pierde timp suplimentar la pornirea aplicației, care este ulterior compensat de funcționarea sa mai rapidă.
În limbaje precum Java , PHP , C# , Lua , Perl , GNU CLISP , codul sursă este tradus într-una dintre reprezentările intermediare numite bytecode . Bytecode nu este codul de mașină al unui anumit procesor și poate fi portat pe diferite arhitecturi de computer și executat exact în același mod. Codul octet este interpretat (executat) de către mașina virtuală . JIT citește bytecode din unele sectoare (rar din toate odată) și le compilează în codul mașinii. Acest sector poate fi un fișier, o funcție sau orice bucată de cod. Odată compilat, codul poate fi stocat în cache și apoi reutilizat fără recompilare.
Un mediu compilat dinamic este un mediu în care compilatorul poate fi apelat de o aplicație în timpul rulării. De exemplu, majoritatea implementărilor Common Lisp conțin o funcție compilecare poate crea o funcție în timpul rulării; în Python, aceasta este o funcție eval. Acest lucru este convenabil pentru programator, deoarece el poate controla ce părți ale codului sunt de fapt compilate. De asemenea, este posibil să compilați codul generat dinamic folosind această tehnică, care în unele cazuri duce la o performanță chiar mai bună decât implementarea în codul compilat static. Cu toate acestea, merită să ne amintim că astfel de funcții pot fi periculoase, mai ales atunci când datele sunt transferate din surse nesigure. [patru]
Scopul principal al utilizării JIT este de a atinge și de a depăși performanța compilației statice, păstrând în același timp beneficiile compilării dinamice:
JIT este în general mai eficient decât interpretarea codului. În plus, în unele cazuri, JIT poate arăta performanțe mai bune în comparație cu compilarea statică datorită optimizărilor care sunt posibile numai în timpul execuției:
Un motiv tipic pentru o întârziere la pornirea unui compilator JIT este cheltuiala cu încărcarea mediului și compilarea aplicației în codul nativ. În general, cu cât JIT-ul efectuează mai multe optimizări și mai bune, cu atât întârzierea va fi mai mare. Prin urmare, dezvoltatorii JIT trebuie să găsească un compromis între calitatea codului generat și timpul de pornire. Cu toate acestea, adesea se dovedește că blocajul în procesul de compilare nu este procesul de compilare în sine, ci întârzierile sistemului I/O (de exemplu, rt.jar în Java Virtual Machine (JVM) are o dimensiune de 40 MB , iar căutarea metadatelor în ele durează destul de mult).
Un alt instrument de optimizare este să compilați numai acele părți ale aplicației care sunt utilizate cel mai des. Această abordare este implementată în HotSpot Java Virtual Machine a PyPy și Sun Microsystems .
Ca euristică, pot fi utilizate numărul de lansări ale secțiunii aplicației, dimensiunea codului de octet sau detectorul de ciclu.
Uneori este greu să găsești compromisul potrivit. De exemplu, mașina virtuală Java a Sun are două moduri de operare - client și server. În modul client, numărul de compilări și optimizări este minim pentru o pornire mai rapidă, în timp ce în modul server, performanța maximă este atinsă, dar din această cauză, timpul de pornire este crescut.
O altă tehnică numită pre-JIT compilează codul înainte de a rula. Avantajul acestei tehnici este timpul redus de pornire, în timp ce dezavantajul este calitatea proastă a codului compilat în comparație cu runtime JIT.
Prima implementare JIT poate fi atribuită LISP, scrisă de McCarthy în 1960 [5] . În cartea sa Funcții recursive ale expresiilor simbolice și calculul lor de către mașină, Partea I , el menționează funcțiile care sunt compilate în timpul execuției, eliminând astfel nevoia de a scoate munca compilatorului pe carduri perforate .
O altă referire timpurie la JIT poate fi atribuită lui Ken Thompson , care în 1968 a fost pionier în utilizarea expresiilor regulate pentru a căuta subșiruri în editorul de text QED . Pentru a accelera algoritmul, Thompson a implementat compilarea expresiilor regulate la codul mașină IBM 7094 .
O metodă pentru obținerea codului compilat a fost propusă de Mitchell în 1970 când a implementat limbajul experimental LC 2 . [6] [7]
Smalltalk (1983) a fost un pionier în tehnologia JIT. Traducerea în codul nativ a fost efectuată la cerere și stocată în cache pentru utilizare ulterioară. Când memoria s-a epuizat, sistemul ar putea elimina o parte din codul stocat în cache din RAM și îl poate restaura atunci când este nevoie din nou. Limbajul de programare Self a fost de ceva vreme cea mai rapidă implementare a Smalltalk și a fost doar de două ori mai lent decât C , fiind complet orientat pe obiecte.
Sinele a fost abandonat de Sun, dar cercetările au continuat în limbajul Java. Termenul „Compilare just-in-time” a fost împrumutat de la termenul din industrie „Just in Time” și popularizat de James Gosling , care a folosit termenul în 1993. [8] JIT este acum folosit în aproape toate implementările Java Virtual Machine .
De mare interes este și teza susținută în 1994 la Universitatea ETH (Elveția, Zurich) de Michael Franz „Dynamic code generation - the key to portable software” [9] și sistemul Juice [10] implementat de acesta pentru generarea dinamică a codului. dintr-un arbore semantic portabil pentru limba Oberon . Sistemul Juice a fost oferit ca plug-in pentru browserele de internet.
Deoarece JIT compune cod executabil din date, există o problemă de securitate și posibile vulnerabilități.
Compilarea JIT implică compilarea codului sursă sau bytecode în codul mașinii și executarea acestuia. De regulă, rezultatul este scris în memorie și executat imediat, fără salvare intermediară pe disc sau apelarea acestuia ca program separat. În arhitecturile moderne, pentru a îmbunătăți securitatea, secțiunile arbitrare ale memoriei nu pot fi executate ca cod de mașină ( NX bit ). Pentru o lansare corectă, regiunile de memorie trebuie marcate în prealabil ca executabile, în timp ce pentru o mai mare securitate, flag-ul de execuție poate fi setat doar după ce steag-ul de permisiune de scriere este eliminat (schema de protecție W^X) [11] .