Compilare JIT

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.

Caracteristici de implementare

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

Descriere

Î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:

  1. Compilarea se poate face direct pentru procesorul și sistemul de operare țintă pe care rulează aplicația. De exemplu, JIT poate folosi extensiile de procesor SSE2 vectoriale dacă detectează suport pentru acestea.
  2. Mediul poate colecta statistici despre programul care rulează și poate face optimizări pe baza acestor informații. Unele compilatoare statice pot lua, de asemenea, informații despre rulările anterioare ale aplicației ca intrare.
  3. Mediul poate face optimizări globale ale codului (cum ar fi integrarea funcțiilor bibliotecii în cod) fără a pierde beneficiile compilării dinamice și fără suprasolicitarea compilatoarelor și linkerelor statice .
  4. Reconstrucție mai ușoară a codului pentru o utilizare mai bună a memoriei cache .

Întârziere la pornire, remedii

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.

Istorie

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.

Securitate

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

Vezi și

Note

  1. Core Java: An Integrated Approach Arhivat 27 august 2017 la Wayback Machine , 2008, ISBN 9788177228366 , Dreamtech Press, 2008. p.12
  2. GNU lightning - Proiectul GNU - Free Software Foundation (FSF) . Preluat la 27 august 2017. Arhivat din original la 19 septembrie 2017.
  3. Benjamin Peterson - PyPy Arhivat pe 12 mai 2008 la Wayback Machine
  4. Și din nou despre pericolul evalului () Copie arhivată din 13 septembrie 2014 pe Wayback Machine , habrahabr
  5. Aycock 2003, 2. JIT Compilation Techniques, 2.1 Geneza, p. 98.
  6. Aycock 2003, 2. JIT Compilation Techniques, 2.2 LC², p. 98-99.
  7. Mitchell, JG (1970). Proiectarea și construcția de sisteme de programare interactivă flexibile și eficiente .
  8. Aycock & 2003 2.14 Java, p. 107, nota de subsol 13.
  9. Michael Franz - OberonCore Arhivat 26 septembrie 2017 la Wayback Machine ; disertație, intitulată „Code Generation On-The-Fly: A Key To Portable Software”, arhivată pe 7 septembrie 2017 la Wayback Machine
  10. Juice - OberonCore . Consultat la 7 noiembrie 2009. Arhivat din original pe 23 decembrie 2009.
  11. Scrieți XOR Execute JIT Support Lands pentru Mozilla Firefox Arhivat 2 august 2017 la Wayback Machine / Phoronix, 4 ianuarie   2016