I compilatori utilizzano il multithreading per tempi di compilazione più rapidi?


16

Se ricordo correttamente il corso dei miei compilatori, il compilatore tipico ha il seguente schema semplificato:

  • Un analizzatore lessicale esegue la scansione (o attiva alcune funzioni di scansione) il codice sorgente carattere per carattere
  • La stringa di caratteri di input viene verificata per verificarne la validità nel dizionario dei lessemi
  • Se il lessico è valido, viene quindi classificato come token a cui corrisponde
  • Il parser convalida la sintassi della combinazione di token; token per token .

È teoricamente possibile dividere il codice sorgente in quarti (o qualunque denominatore) e multithread del processo di scansione e analisi? Esistono compilatori che utilizzano il multithreading?




1
@RobertHarvey La prima risposta del primo link ha scritto "ma i compilatori stessi sono ancora a thread singolo". Quindi questo è un no?
8protons,

Ti suggerisco di leggere il resto delle risposte, specialmente questa , e il secondo link che ho pubblicato.
Robert Harvey,

2
@RobertHarvey Il secondo link che hai pubblicato, dalla mia comprensione di ciò che sta dicendo, sta parlando di un compilatore che genera una versione multithread della tua applicazione compilata. Non si tratta del compilatore stesso. Grazie per le risorse condivise e per il tempo dedicato a rispondere.
8protons,

Risposte:


29

I grandi progetti software sono generalmente composti da molte unità di compilazione che possono essere compilate in modo relativamente indipendente, quindi la compilazione è spesso parallelizzata con una granularità molto approssimativa invocando il compilatore più volte in parallelo. Ciò accade a livello dei processi del sistema operativo ed è coordinato dal sistema di generazione piuttosto che dal compilatore. Mi rendo conto che questo non è quello che hai chiesto, ma è la cosa più vicina alla parallelizzazione nella maggior parte dei compilatori.

Perché? Bene, gran parte del lavoro svolto dai compilatori non si presta facilmente alla parallelizzazione:

  • Non puoi semplicemente dividere l'input in più blocchi e lix in modo indipendente. Per semplicità, ti consigliamo di dividere sui limiti dei lexme (in modo che nessun thread inizi nel mezzo di un lexme), ma determinare i limiti dei lexme richiede potenzialmente molto contesto. Ad esempio, quando salti nel mezzo del file, devi assicurarti di non saltare in una stringa letterale. Ma per controllare questo, devi guardare praticamente tutti i personaggi che sono venuti prima, il che è quasi tanto lavoro quanto semplicemente lasciarlo per cominciare. Inoltre, il lexing è raramente il collo di bottiglia nei compilatori per le lingue moderne.
  • L'analisi è ancora più difficile da parallelizzare. Tutti i problemi di divisione del testo di input per il lexing si applicano ancora di più alla divisione dei token per l'analisi --- ad esempio, determinare da dove inizia una funzione è fondamentalmente difficile quanto analizzare il contenuto della funzione per cominciare. Anche se potrebbero esserci dei modi per aggirare questo, probabilmente saranno sproporzionatamente complessi per il piccolo vantaggio. Anche l'analisi non è il maggiore collo di bottiglia.
  • Dopo aver analizzato, di solito è necessario eseguire la risoluzione dei nomi, ma questo porta a un'enorme rete intrecciata di relazioni. Per risolvere una chiamata al metodo qui, potresti dover prima risolvere le importazioni in questo modulo, ma quelle richiedono la risoluzione dei nomi in un'altra unità di compilazione, ecc. Lo stesso vale per l'inferenza del tipo se la tua lingua lo possiede.

Dopo questo, diventa leggermente più facile. Il controllo e l'ottimizzazione del tipo e la generazione del codice potrebbero, in linea di principio, essere parallelizzati alla granularità della funzione. Ne conosco ancora pochi se qualche compilatore lo fa, forse perché svolgere qualsiasi compito così grande contemporaneamente è piuttosto impegnativo. Devi anche considerare che la maggior parte dei progetti software di grandi dimensioni contiene così tante unità di compilazione che l'approccio "esegui un gruppo di compilatori in parallelo" è del tutto sufficiente per mantenere occupati tutti i tuoi core (e in alcuni casi, anche un'intera server farm). Inoltre, nelle attività di compilazione di grandi dimensioni, l'I / O del disco può rappresentare un collo di bottiglia tanto quanto il lavoro effettivo di compilazione.

Detto questo, conosco un compilatore che parallelizza il lavoro di generazione e ottimizzazione del codice. Il compilatore Rust può dividere il lavoro di back-end (LLVM, che in realtà include ottimizzazioni del codice che sono tradizionalmente considerate "di fascia media") tra diversi thread. Questo si chiama "unità code-gen". Contrariamente alle altre possibilità di parallelizzazione discusse sopra, questo è economico perché:

  1. Il linguaggio ha unità di compilazione piuttosto grandi (rispetto, per esempio, a C o Java), quindi potrebbero esserci meno unità di compilazione in volo rispetto a quelle dei core.
  2. La parte che viene parallelizzata richiede in genere la maggior parte del tempo di compilazione.
  3. Il lavoro di back-end è, per la maggior parte, imbarazzantemente parallelo: basta ottimizzare e tradurre in codice macchina ogni funzione in modo indipendente. Naturalmente ci sono ottimizzazioni interproceduali e le unità codegen ostacolano quelle e quindi incidono sulle prestazioni, ma non ci sono problemi semantici.

2

La compilazione è un problema "imbarazzantemente parallelo".

A nessuno importa il tempo per la compilazione di un file. Alla gente interessa il tempo di compilare 1000 file. E per 1000 file, ogni core del processore può compilare felicemente un file alla volta, mantenendo tutti i core totalmente occupati.

Suggerimento: "make" utilizza più core se si fornisce l'opzione della riga di comando corretta. Senza questo compilerà un file dopo l'altro su un sistema a 16 core. Ciò significa che puoi farlo compilare 16 volte più velocemente con una modifica di una riga alle opzioni di compilazione.

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.