In che modo un compilatore JIT è diverso da un normale compilatore?


22

C'è stato molto clamore sui compilatori JIT per linguaggi come Java, Ruby e Python. In che modo i compilatori JIT sono diversi dai compilatori C / C ++ e perché i compilatori scritti per Java, Ruby o Python sono chiamati compilatori JIT, mentre i compilatori C / C ++ sono semplicemente chiamati compilatori?

Risposte:


17

I compilatori JIT compilano il codice al volo, subito prima della loro esecuzione o anche quando sono già in esecuzione. In questo modo, la VM in cui è in esecuzione il codice può verificare la presenza di modelli nell'esecuzione del codice per consentire ottimizzazioni che sarebbero possibili solo con le informazioni di runtime. Inoltre, se la VM decide che la versione compilata non è abbastanza buona per qualsiasi motivo (ad esempio, troppi errori nella cache o codice che genera frequentemente una particolare eccezione), può decidere di ricompilarla in un modo diverso, portando a una soluzione molto più intelligente compilazione.

Dall'altro lato, i compilatori C e C ++ non sono tradizionalmente JIT. Compilano in un solo colpo solo una volta sulla macchina dello sviluppatore e quindi viene prodotto un eseguibile.


Esistono compilatori JIT che tengono traccia dei mancati cache e adattano di conseguenza la loro strategia di compilazione? @Victor
Martin Berger

@MartinBerger Non so se qualche compilatore JIT disponibile lo faccia, ma perché no? Man mano che i computer sono più potenti e si sviluppa l'informatica, le persone possono applicare più ottimizzazioni al programma. Ad esempio, quando Java è nato, è molto lento, forse 20 volte rispetto a quelli compilati, ma ora le prestazioni non sono molto in ritardo e potrebbero essere paragonabili ad alcuni compilatori
phuclv

L'ho sentito su un post sul blog molto tempo fa. Il compilatore può compilare in modo diverso a seconda della CPU corrente. Ad esempio, può vettorizzare automaticamente alcuni codici se rileva che la CPU supporta SSE / AVX. Quando sono disponibili nuove estensioni SIMD, queste possono essere compilate con quelle più recenti, quindi il programmatore non deve modificare nulla. O se è in grado di organizzare le operazioni / i dati per sfruttare la cache più grande o per ridurre la mancanza della cache
phuclv

@ LưuVĩnhPhúc Chào! Sono felice di crederci, ma la differenza è che, ad esempio, il tipo di CPU o la dimensione della cache sono qualcosa di statico che non cambia durante il calcolo, quindi potrebbe essere facilmente eseguito anche da un compilatore statico. La cache manca OTOH sono molto dinamici.
Martin Berger,

12

JIT è l'abbreviazione di compilatore just-in-time e name is misson: durante il runtime, determina ottimizzazioni del codice utili e le applica. Non sostituisce i normali compilatori ma fanno parte degli interpreti. Si noti che linguaggi come Java che utilizzano il codice intermedio hanno entrambi : un normale compilatore per la traduzione di codice da sorgente a intermedio e un JIT incluso nell'interprete per aumentare le prestazioni.

Le ottimizzazioni del codice possono certamente essere eseguite da compilatori "classici", ma nota la differenza principale: i compilatori JIT hanno accesso ai dati in fase di esecuzione. Questo è un enorme vantaggio; sfruttarlo correttamente può essere difficile, ovviamente.

Considera, ad esempio, un codice come questo:

m(a : String, b : String, k : Int) {
  val c : Int;
  switch (k) {
    case 0 : { c = 7; break; }
    ...
    case 17 : { c = complicatedMethod(k, a+b); break; }
  }

  return a.length + b.length - c + 2*k;
}

Un normale compilatore non può fare troppo al riguardo. Un compilatore JIT, tuttavia, può rilevare che mviene sempre chiamato solo k==0per qualche motivo (cose del genere possono accadere come modifiche del codice nel tempo); può quindi creare una versione più piccola del codice (e compilarlo in codice nativo, anche se considero questo un punto minore, concettualmente):

m(a : String, b : String) {
  return a.length + b.length - 7;
}

A questo punto, probabilmente incorporerà anche la chiamata del metodo in quanto è banale ora.

Apparentemente, il Sun ha respinto la maggior parte delle ottimizzazioni che si javacusavano in Java 6; Mi è stato detto che quelle ottimizzazioni hanno reso difficile per JIT fare molto, e alla fine il codice compilato ingenuamente ha funzionato più velocemente. Vai a capire.


A proposito, in presenza della cancellazione dei tipi (es. Generici in Java) JIT è in realtà uno svantaggio dei tipi wrt.
Raffaello

Quindi il runtime è più complicato di quello per un linguaggio compilato perché l'ambiente di runtime deve raccogliere dati per ottimizzare.
Saadtaame

2
In molti casi, un JIT non sarebbe in grado di sostituire mcon una versione che non ha verificato kpoiché non sarebbe in grado di dimostrare che nonm sarebbe mai stato chiamato con un valore diverso da zero k, ma anche senza essere in grado di dimostrare che potrebbe sostituire con static miss_count; if (k==0) return a.length+b.length-7; else if (miss_count++ < 16) { ... unoptimized code for m ...} else { ... consider other optimizations...}.
Supercat,

1
Sono d'accordo con @supercat e penso che il tuo esempio sia in realtà piuttosto fuorviante. Solo se k=0 sempre , nel senso che non è una funzione dell'input o dell'ambiente, è sicuro abbandonare il test, ma determinarlo richiede un'analisi statica, che è molto costosa e quindi accessibile solo al momento della compilazione. Una JIT può vincere quando un percorso attraverso un blocco di codice viene preso molto più spesso di altri e una versione del codice specializzata per questo percorso sarebbe molto più veloce. Ma JIT emetterà comunque un test per verificare se si applica il percorso rapido e, in caso contrario, prenderà il "percorso lento".
j_random_hacker il

Un esempio: una funzione accetta un Base* pparametro e chiama funzioni virtuali attraverso di esso; l'analisi di runtime mostra che l'oggetto reale puntato sempre (o quasi sempre) sembra essere di Derived1tipo. La JIT potrebbe produrre una nuova versione della funzione con chiamate ai Derived1metodi risolte staticamente (o persino in linea) . Questo codice sarebbe preceduto da un condizionale che controlla se pil puntatore vtable punta alla Derived1tabella prevista ; in caso contrario, passa invece alla versione originale della funzione con le sue chiamate di metodo risolte dinamicamente più lente.
j_random_hacker il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.