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

13

Se ricordo correttamente il mio corso su compilatori, il tipico compilatore ha il seguente schema semplificato:

  • Un analizzatore lessicale esegue la scansione (o chiama una funzione di scansione su) il codice sorgente carattere per carattere
  • La stringa di caratteri di input viene confrontata con il dizionario dei lessemi per la validità
  • Se il lexeme è valido, viene quindi classificato come token che corrisponde a
  • Il parser convalida la sintassi della combinazione di token; token-by-token .

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

    
posta 8protons 16.06.2016 - 23:10
fonte

2 risposte

25

I progetti software di grandi dimensioni sono solitamente composti da molte unità di compilazione che possono essere compilate in modo relativamente indipendente, e quindi la compilazione è spesso parallelizzata in 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 compilazione piuttosto che dal compilatore corretto. Mi rendo conto che questo non è quello che hai chiesto ma è la cosa più simile alla parallelizzazione nella maggior parte dei compilatori.

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

  • Non puoi semplicemente dividere l'input in diversi blocchi e renderli lessicali in modo indipendente. Per semplicità vorrai dividere i confini delle lexme (in modo che nessun thread inizi nel mezzo di un lexme), ma determinare i limiti delle lexme potenzialmente richiede molto contesto. Ad esempio, quando salti nel mezzo del file, devi assicurarti di non saltare in una stringa letterale. Ma per verificare questo, devi guardare praticamente tutti i personaggi che sono venuti prima, il che è quasi tanto lavoro quanto semplicemente lessico per cominciare. Inoltre, il lexing è raramente il collo di bottiglia nei compilatori per i linguaggi moderni.
  • 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 dove inizia una funzione è fondamentalmente difficile quanto analizzare i contenuti della funzione per iniziare. Anche se potrebbero esserci modi per aggirare questo, saranno probabilmente sproporzionatamente complessi per il piccolo beneficio. Anche l'analisi non è il collo di bottiglia più grande.
  • Dopo aver analizzato, di solito è necessario eseguire la risoluzione dei nomi, ma ciò porta a una grande rete intrecciata di relazioni. Per risolvere una chiamata di metodo qui potrebbe essere necessario risolvere prima 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 lingua lo possiede.

Dopo questo, diventa leggermente più facile. Il controllo dei tipi e l'ottimizzazione e la generazione del codice potrebbero, in linea di principio, essere parallelizzati alla granularità della funzione. Conosco ancora pochi compilatori che lo fanno, forse perché svolgere qualsiasi compito così grande contemporaneamente è piuttosto impegnativo. È inoltre necessario considerare che la maggior parte dei progetti software di grandi dimensioni contengono così tante unità di compilazione che l'approccio "eseguire un gruppo di compilatori in parallelo" è interamente sufficiente per mantenere tutti i core occupati (e in alcuni casi anche un'intera server farm). Inoltre, nelle attività di compilazione di grandi dimensioni, l'I / O del disco può costituire un collo di bottiglia quanto il lavoro effettivo di compilazione.

Tutto ciò che ho detto, so di un compilatore che parallelizza il lavoro di generazione e ottimizzazione del codice. Il compilatore di Rust può dividere il lavoro di back-end (LLVM, che in realtà include ottimizzazioni di codice che sono tradizionalmente considerate "middle-end") tra diversi thread. Questo si chiama "unità code-gen". In contrasto con le altre possibilità di parallelizzazione sopra discusse, questo è economico perché:

  1. Il linguaggio ha unità di compilazione piuttosto grandi (rispetto a, ad esempio, C o Java), quindi potrebbero esserci meno unità di compilazione in volo rispetto a quelle disponibili.
  2. La parte che viene parallelizzata di solito richiede la maggior parte del tempo di compilazione.
  3. Il back-end di lavoro è, per la maggior parte, imbarazzantemente parallelo: basta ottimizzare e tradurre a codice macchina ogni funzione in modo indipendente. Ci sono ovviamente ottimizzazioni inter-procedurali, e le unità codegen ne ostacolano e quindi influenzano le prestazioni, ma non ci sono problemi semantici.
risposta data 16.06.2016 - 23:36
fonte
2

La compilazione è un problema "imbarazzantemente parallelo".

A nessuno importa del tempo per la compilazione di un file. Le persone si preoccupano del 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" usa più core se gli dai la giusta opzione da linea di comando. Senza di ciò compila un file dopo l'altro su un sistema a 16 core. Il che significa che puoi farlo compilare 16 volte più velocemente con una modifica di una riga alle opzioni di costruzione.

    
risposta data 17.06.2016 - 11:05
fonte

Leggi altre domande sui tag