Hai semplificato troppo l'affermazione di Guido nel formulare la tua domanda. Il problema non è scrivere un compilatore per un linguaggio tipizzato in modo dinamico. Il problema è quello di scriverne uno che sia (criterio 1) sempre corretto, (criterio 2) mantiene la digitazione dinamica e (criterio 3) è notevolmente più veloce per una quantità significativa di codice.
È facile implementare il 90% (in mancanza dei criteri 1) di Python ed essere costantemente veloce. Allo stesso modo, è facile creare una variante Python più veloce con la digitazione statica (criterio 2 non riuscito). L'implementazione del 100% è anche facile (nella misura in cui è facile implementare un linguaggio così complesso), ma finora ogni modo semplice di implementarlo risulta relativamente lento (in mancanza dei criteri 3).
L'implementazione di un interprete più JIT che è corretta, implementa l'intero linguaggio ed è più veloce per alcuni codici risulta fattibile, sebbene significativamente più difficile (cfr. PyPy) e solo così se automatizzi la creazione del compilatore JIT (Psyco ha fatto a meno , ma era molto limitato in quale codice poteva accelerare). Ma nota che questo è esplicitamente fuori portata, poiché stiamo parlando di elettricità staticacompilatori (noti anche in anticipo). Cito solo questo per spiegare perché il suo approccio non funziona per i compilatori statici (o almeno non esiste un controesempio esistente): deve prima interpretare e osservare il programma, quindi generare codice per una iterazione specifica di un ciclo (o un altro codice lineare percorso), quindi ottimizza l'inferno in base a ipotesi vere solo per quella specifica iterazione (o almeno, non per tutte le possibili iterazioni). L'aspettativa è che molte esecuzioni successive di quel codice corrispondano anche alle aspettative e quindi traggono vantaggio dalle ottimizzazioni. Alcuni controlli (relativamente economici) vengono aggiunti per garantire la correttezza. Per fare tutto ciò, è necessario avere un'idea di cosa specializzarsi e un'implementazione lenta ma generale a cui ricorrere. I compilatori AOT non hanno nessuno dei due. Non possono specializzarsi affattoin base al codice che non possono vedere (ad es. codice caricato dinamicamente) e specializzarsi con noncuranza significa generare più codice, il che presenta una serie di problemi (utilizzo di icache, dimensione binaria, tempo di compilazione, rami aggiuntivi).
L'implementazione di un compilatore AOT che implementa correttamente l' intera lingua è anche relativamente semplice: generare codice che chiama nel runtime per fare ciò che l'interprete farebbe se alimentato con questo codice. Nuitka (principalmente) lo fa. Tuttavia, ciò non produce molti vantaggi in termini di prestazioni (in mancanza dei criteri 3), poiché devi ancora fare tutto il lavoro non necessario di un interprete, salvo per inviare il bytecode al blocco di codice C che fa quello che hai compilato. questo è solo un costo piuttosto piccolo - abbastanza significativo da valere la pena di essere ottimizzato in un interprete esistente, ma non abbastanza significativo da giustificare una nuova implementazione con i suoi problemi.
Cosa sarebbe necessario per soddisfare tutti e tre i criteri? Non ne abbiamo idea Esistono alcuni schemi di analisi statica che possono estrarre alcune informazioni su tipi concreti, flusso di controllo, ecc. Dai programmi Python. Quelli che producono dati accurati oltre l'ambito di un singolo blocco di base sono estremamente lenti e devono vedere l'intero programma, o almeno la maggior parte di esso. Tuttavia, non puoi fare molto con queste informazioni, tranne forse ottimizzare alcune operazioni sui tipi predefiniti.
Perché? Per dirla senza mezzi termini, un compilatore rimuove la possibilità di eseguire il codice Python caricato in fase di esecuzione (criteri 1 non soddisfacenti) o non fa alcuna ipotesi che può essere invalidata da qualsiasi codice Python. Sfortunatamente, ciò include praticamente tutto ciò che è utile per l'ottimizzazione dei programmi: i globi, incluse le funzioni, possono essere rimbalzati, le classi possono essere mutate o sostituite completamente, i moduli possono anche essere arbitrariamente modificati, l'importazione può essere dirottata in diversi modi, ecc. Una singola stringa passata a eval
, exec
, __import__
o numerose altre funzioni, possono fare qualsiasi di queste cose. In effetti, ciò significa che quasi nessuna grande ottimizzazione può essere applicata, producendo scarsi benefici in termini di prestazioni (fallimento dei criteri 3). Torna al paragrafo precedente.