Ecco alcuni dei miei dati aggiornati, sebbene ristretti, con GCC 4.7.2 e Clang 3.2 per C ++.
AGGIORNAMENTO: confronto GCC 4.8.1 v clang 3.3 allegato di seguito.
AGGIORNAMENTO: il confronto GCC 4.8.2 v clang 3.4 viene aggiunto a questo.
Mantengo uno strumento OSS creato per Linux con GCC e Clang e con il compilatore di Microsoft per Windows. Lo strumento, coan, è un preprocessore e analizzatore di file sorgente C / C ++ e codeline di tali: il suo profilo computazionale si specializza in analisi ricorsive-discendenza e gestione dei file. Il ramo di sviluppo (a cui appartengono questi risultati) comprende attualmente circa 11K LOC in circa 90 file. È codificato, ora, in C ++ che è ricco di polimorfismo e modelli e, ma è ancora impantanato in molte patch dal suo passato non così lontano nel C. unito La semantica non viene espressamente sfruttata. È a thread singolo. Non ho dedicato alcuno sforzo per ottimizzarlo, mentre l '"architettura" rimane così ampiamente da fare.
Ho usato Clang prima della 3.2 solo come compilatore sperimentale perché, nonostante la sua velocità di compilazione e diagnostica superiori, il suo supporto standard C ++ 11 era in ritardo rispetto alla versione GCC contemporanea sotto gli aspetti esercitati da Coan. Con 3.2, questo divario è stato chiuso.
Il mio cablaggio di test Linux per gli attuali processi di sviluppo di Coan elabora circa 70.000 file di sorgenti in una combinazione di casi di test del parser a un file, stress test che consumano migliaia di file e test di scenario che consumano <1 KB di file. Oltre a riportare i risultati del test, il cablaggio si accumula e visualizza i totali dei file consumati e il tempo di esecuzione consumato in Coan (passa semplicemente ogni riga di comando Coan al comando Linux time
e acquisisce e somma i numeri riportati). I tempi sono lusingati dal fatto che qualsiasi numero di test che impiegano 0 tempo misurabile si sommerà a 0, ma il contributo di tali test è trascurabile. Le statistiche dei tempi sono visualizzate alla fine in make check
questo modo:
coan_test_timer: info: coan processed 70844 input_files.
coan_test_timer: info: run time in coan: 16.4 secs.
coan_test_timer: info: Average processing time per input file: 0.000231 secs.
Ho confrontato le prestazioni del cablaggio di prova tra GCC 4.7.2 e Clang 3.2, a parità di condizioni tranne i compilatori. A partire da Clang 3.2, non ho più bisogno di alcuna differenziazione del preprocessore tra tratti di codice che GCC compilerà e alternative di Clang. Ho creato la stessa libreria C ++ (GCC) in ogni caso e ho eseguito tutti i confronti consecutivamente nella stessa sessione terminale.
Il livello di ottimizzazione predefinito per la mia build di rilascio è -O2. Ho anche testato con successo build a -O3. Ho testato ogni configurazione 3 volte back-to-back e ho mediato i 3 risultati, con i seguenti risultati. Il numero in una cella di dati è il numero medio di microsecondi consumati dall'eseguibile coan per elaborare ciascuno dei file di input ~ 70K (lettura, analisi e scrittura dell'output e della diagnostica).
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.7.2 | 231 | 237 |0.97 |
----------|-----|-----|-----|
Clang-3.2 | 234 | 186 |1.25 |
----------|-----|-----|------
GCC/Clang |0.99 | 1.27|
È molto probabile che ogni particolare applicazione abbia tratti che giocano ingiustamente ai punti di forza o di debolezza di un compilatore. Il benchmarking rigoroso impiega diverse applicazioni. Tenendo ben presente ciò, le caratteristiche degne di nota di questi dati sono:
- -L'ottimizzazione dell'O3 è stata leggermente dannosa per GCC
- -L'ottimizzazione dell'O3 è stata molto utile per Clang
- Con l'ottimizzazione di -O2, GCC è stato più veloce di Clang con un semplice baffo
- Con l'ottimizzazione -O3, Clang era molto più veloce di GCC.
Un ulteriore interessante confronto tra i due compilatori è emerso per caso poco dopo questi risultati. Coan impiega generosamente puntatori intelligenti e uno di questi è fortemente esercitato nella gestione dei file. Questo particolare tipo di puntatore intelligente era stato digitato in versioni precedenti per motivi di differenziazione del compilatore, per essere un std::unique_ptr<X>
se il compilatore configurato avesse un supporto sufficientemente maturo per il suo utilizzo come quello, e altrimenti un std::shared_ptr<X>
. L'inclinazione è std::unique_ptr
stata insensata, dal momento che questi puntatori erano in effetti trasferiti in giro, ma std::unique_ptr
sembrava l'opzione più adatta per la sostituzione
std::auto_ptr
in un momento in cui le varianti di C ++ 11 erano nuove per me.
Nel corso di build sperimentali per valutare la continua necessità di Clang 3.2 di questa e di una simile differenziazione, ho inavvertitamente costruito
std::shared_ptr<X>
quando avevo intenzione di costruire std::unique_ptr<X>
, e sono stato sorpreso di osservare che l'eseguibile risultante, con ottimizzazione predefinita -O2, era il più veloce che io aveva visto, a volte raggiungendo 184 msec. per file di input. Con questa modifica al codice sorgente, i risultati corrispondenti sono stati questi;
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.7.2 | 234 | 234 |1.00 |
----------|-----|-----|-----|
Clang-3.2 | 188 | 187 |1.00 |
----------|-----|-----|------
GCC/Clang |1.24 |1.25 |
I punti di nota qui sono:
- Nessuno dei due compilatori ora beneficia affatto dell'ottimizzazione -O3.
- Clang batte GCC altrettanto importante per ogni livello di ottimizzazione.
- Le prestazioni di GCC sono influenzate solo marginalmente dalla modifica del tipo di puntatore intelligente.
- Le prestazioni di Clang -O2 sono influenzate in modo significativo dalla modifica del tipo di puntatore intelligente.
Prima e dopo la modifica del tipo di puntatore intelligente, Clang è in grado di creare un eseguibile coan sostanzialmente più veloce con ottimizzazione -O3 e può creare un eseguibile ugualmente più veloce con -O2 e -O3 quando quel tipo di puntatore è il migliore - std::shared_ptr<X>
- per il lavoro.
Una domanda ovvia su cui non sono competente a commentare è perché
Clang dovrebbe essere in grado di trovare una velocità del 25% -O2 nella mia applicazione quando un tipo di puntatore intelligente molto usato viene cambiato da unico a condiviso, mentre GCC è indifferente allo stesso cambiamento. Né so se dovrei esultare o rincuorare la scoperta che l'ottimizzazione -O2 di Clang nasconde una sensibilità così grande alla saggezza delle mie scelte di puntatore intelligente.
AGGIORNAMENTO: GCC 4.8.1 v clang 3.3
I risultati corrispondenti ora sono:
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.1 | 442 | 443 |1.00 |
----------|-----|-----|-----|
Clang-3.3 | 374 | 370 |1.01 |
----------|-----|-----|------
GCC/Clang |1.18 |1.20 |
Il fatto che ora tutti e quattro i file eseguibili impieghino un tempo medio molto più lungo rispetto a prima per elaborare il file 1 non riflette le prestazioni degli ultimi compilatori. È dovuto al fatto che il ramo di sviluppo successivo dell'applicazione di test ha assunto nel frattempo molta raffinatezza di analisi e la paga rapidamente. Solo i rapporti sono significativi.
I punti di nota ora non sono in modo accattivante:
- GCC è indifferente all'ottimizzazione -O3
- clang beneficia molto marginalmente dell'ottimizzazione -O3
- clang batte GCC con un margine altrettanto importante ad ogni livello di ottimizzazione.
Confrontando questi risultati con quelli di GCC 4.7.2 e clang 3.2, si nota che GCC ha recuperato circa un quarto del vantaggio di clang ad ogni livello di ottimizzazione. Ma dal momento che l'applicazione di test è stata fortemente sviluppata nel frattempo, non si può attribuire con sicurezza questo a un recupero nella generazione del codice di GCC. (Questa volta, ho notato l'istantanea dell'applicazione da cui sono stati ottenuti i tempi e posso riutilizzarla.)
AGGIORNAMENTO: GCC 4.8.2 v clang 3.4
Ho terminato l'aggiornamento per GCC 4.8.1 v Clang 3.3 dicendo che mi sarei attaccato alla stessa fotografia istantanea per ulteriori aggiornamenti. Ma ho deciso invece di provare su quella istantanea (rev. 301) e sull'ultima istantanea di sviluppo che ho superato la sua suite di test (rev. 619). Questo dà ai risultati un po 'di longitudine e ho avuto un altro motivo:
Il mio post originale ha notato che non avevo dedicato alcuno sforzo all'ottimizzazione della velocità per la navigazione. Questo era ancora il caso del rev. 301. Tuttavia, dopo aver incorporato l'apparato di cronometraggio nell'imbracatura del test di controllo, ogni volta che ho eseguito la suite di test, l'impatto sulle prestazioni degli ultimi cambiamenti mi ha fissato in faccia. Ho visto che spesso era sorprendentemente grande e che la tendenza era molto più negativa di quanto mi sentissi meritato dai guadagni in termini di funzionalità.
Di rev. 308 il tempo medio di elaborazione per file di input nella suite di test era ben più che raddoppiato dalla prima pubblicazione qui. A quel punto ho fatto un'inversione di marcia sulla mia politica decennale di non preoccuparmi delle prestazioni. Nell'ondata intensa di revisioni fino a 619 prestazioni è sempre stata una considerazione e un gran numero di loro è andato semplicemente a riscrivere i portatori di carico chiave su linee fondamentalmente più veloci (anche se senza usare funzioni di compilatore non standard per farlo). Sarebbe interessante vedere la reazione di ciascun compilatore a questa inversione a U,
Ecco la matrice dei tempi ormai familiare per le ultime due build di rev.301 dei compilatori:
coan - rev.301 risultati
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.2 | 428 | 428 |1.00 |
----------|-----|-----|-----|
Clang-3.4 | 390 | 365 |1.07 |
----------|-----|-----|------
GCC/Clang | 1.1 | 1.17|
La storia qui è cambiata solo marginalmente da GCC-4.8.1 e Clang-3.3. La proiezione di GCC è un po 'meglio. Clang è un po 'peggio. Il rumore potrebbe giustificare questo. Clang esce ancora avanti -O2
e -O3
margini che non importerebbero nella maggior parte delle applicazioni ma ne conterebbero parecchi.
Ed ecco la matrice per il rev. 619.
coan - rev.619 risultati
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.2 | 210 | 208 |1.01 |
----------|-----|-----|-----|
Clang-3.4 | 252 | 250 |1.01 |
----------|-----|-----|------
GCC/Clang |0.83 | 0.83|
Accostando le figure 301 e 619, parecchi punti parlano chiaro.
Avevo l'obiettivo di scrivere un codice più veloce ed entrambi i compilatori confermano con enfasi i miei sforzi. Ma:
GCC ripaga quegli sforzi molto più generosamente di Clang. Al -O2
ottimizzazione di Clang 619 build è 46% più veloce rispetto al suo 301 costruire: a -O3
miglioramento di Clang è del 31%. Bene, ma a ogni livello di ottimizzazione la build 619 di GCC è più del doppio della sua 301.
GCC più che inverte la precedente superiorità di Clang. E ad ogni livello di ottimizzazione GCC ora batte Clang del 17%.
La capacità di Clang nella build 301 di ottenere più leva di GCC -O3
dall'ottimizzazione è sparita nella build 619. Nessuno dei due compilatori guadagna significativamente da -O3
.
Sono stato sufficientemente sorpreso da questo capovolgimento di fortune che sospettavo di aver potuto accidentalmente creare una costruzione lenta dello stesso clang 3.4 (da quando l'ho costruito dalla fonte). Quindi ho eseguito nuovamente il test 619 con il Clang 3.3 di serie della mia distribuzione. I risultati erano praticamente gli stessi di 3.4.
Quindi per quanto riguarda la reazione all'inversione a U: sui numeri qui, Clang ha fatto molto meglio di GCC a sottrarre velocità dal mio codice C ++ quando non gli ho dato alcun aiuto. Quando ho deciso di aiutare, GCC ha fatto un lavoro molto migliore di Clang.
Non elevo tale osservazione in un principio, ma prendo la lezione che "Quale compilatore produce i binari migliori?" è una domanda che, anche se si specifica la suite di test a cui la risposta deve essere relativa, non è ancora una questione netta di temporizzazione dei binari.
Il tuo binario migliore è il binario più veloce o è quello che meglio compensa il codice a basso costo? O meglio compensa il
codice costoso che dà priorità alla manutenibilità e al riutilizzo rispetto alla velocità? Dipende dalla natura e dai pesi relativi dei tuoi motivi per produrre il binario e dai vincoli sotto i quali lo fai.
E in ogni caso, se ti preoccupi profondamente di costruire i binari "migliori", allora è meglio che tu continui a controllare come le iterazioni successive di compilatori trasmettono la tua idea di "il migliore" rispetto alle successive iterazioni del tuo codice.