Quali tecniche possono essere utilizzate per accelerare i tempi di compilazione del C ++?


249

Quali tecniche possono essere utilizzate per accelerare i tempi di compilazione del C ++?

Questa domanda è emersa in alcuni commenti sullo stile di programmazione C ++ di Stack Overflow , e sono interessato a sapere quali idee ci sono.

Ho visto una domanda correlata, perché la compilazione C ++ richiede così tanto tempo? , ma ciò non fornisce molte soluzioni.


1
Potresti darci qualche contesto? O stai cercando risposte molto generali?
Pirolistico il

1
Molto simile a questa domanda: stackoverflow.com/questions/364240/…
Adam Rosenfield,

Risposte generali. Ho una base di codice davvero grande scritta da molte persone. Idee su come attaccare sarebbe buono. Inoltre, i suggerimenti per mantenere veloci le compilazioni per il codice appena scritto sarebbero interessanti.
Scott Langham,

Si noti che spesso una parte rilevante del tempo di costruzione non viene utilizzato dal compilatore, ma dagli script di build
Thi gg

1
Ho sfogliato questa pagina e non ho visto alcuna menzione delle misurazioni. Ho scritto un piccolo script di shell che aggiunge un timestamp a ogni riga di input che riceve, quindi posso semplicemente inserire la chiamata "make". Questo mi permette di vedere quali target sono i più costosi, il tempo totale di compilazione o di collegamento, ecc. Solo confrontando i timestamp. Se provi questo approccio, ricorda che i timestamp non saranno precisi per build parallele.
John P,

Risposte:


257

Tecniche linguistiche

Pimpl Idiom

Dai un'occhiata al linguaggio Pimpl qui e qui , noto anche come puntatore opaco o classi di handle. Non solo accelera la compilazione, ma aumenta anche la sicurezza delle eccezioni se combinata con una funzione di scambio senza lancio . Il linguaggio Pimpl consente di ridurre le dipendenze tra le intestazioni e riduce la quantità di ricompilazione che deve essere eseguita.

Dichiarazioni in avanti

Ove possibile, utilizzare dichiarazioni a termine . Se il compilatore deve solo sapere che si SomeIdentifiertratta di una struttura o di un puntatore o altro, non includere l'intera definizione, costringendo il compilatore a fare più lavoro del necessario. Questo può avere un effetto a cascata, rendendolo così più lento di quanto debbano essere.

I I / O flussi sono particolarmente noti per rallentare costruisce. Se ne hai bisogno in un file di intestazione, prova #including <iosfwd>anziché <iostream>e #include l' <iostream>intestazione solo nel file di implementazione. L' <iosfwd>intestazione contiene solo dichiarazioni anticipate. Sfortunatamente le altre intestazioni standard non hanno una rispettiva intestazione delle dichiarazioni.

Preferisce il pass-by-reference al pass-by-value nelle firme di funzione. Questo eliminerà la necessità di #includere le rispettive definizioni di tipo nel file di intestazione e dovrai solo dichiarare in avanti il ​​tipo. Naturalmente, preferisci riferimenti costanti a riferimenti non costanti per evitare bug oscuri, ma questo è un problema per un'altra domanda.

Condizioni di guardia

Utilizzare le condizioni di protezione per impedire che i file di intestazione vengano inclusi più di una volta in una singola unità di traduzione.

#pragma once
#ifndef filename_h
#define filename_h

// Header declarations / definitions

#endif

Utilizzando sia pragma che ifndef, si ottiene la portabilità della semplice soluzione macro, nonché l'ottimizzazione della velocità di compilazione che alcuni compilatori possono eseguire in presenza della pragma oncedirettiva.

Ridurre l'interdipendenza

Più modulare e meno interdipendente è la progettazione del codice in generale, meno spesso dovrai ricompilare tutto. Puoi anche finire per ridurre la quantità di lavoro che il compilatore deve fare su ogni singolo blocco allo stesso tempo, in virtù del fatto che ha meno da tenere traccia.

Opzioni del compilatore

Intestazioni precompilate

Questi sono usati per compilare una sezione comune delle intestazioni incluse una volta per molte unità di traduzione. Il compilatore lo compila una volta e salva il suo stato interno. Tale stato può quindi essere caricato rapidamente per ottenere un vantaggio iniziale nella compilazione di un altro file con lo stesso set di intestazioni.

Fai attenzione a includere solo le cose modificate raramente nelle intestazioni precompilate, o potresti finire per fare ricostruzioni complete più spesso del necessario. Questo è un buon posto per le intestazioni STL e altri file di libreria inclusi.

ccache è un'altra utility che sfrutta le tecniche di memorizzazione nella cache per velocizzare le cose.

Usa il parallelismo

Molti compilatori / IDE supportano l'utilizzo di più core / CPU per eseguire la compilazione contemporaneamente. In GNU Make (di solito usato con GCC), usa l' -j [N]opzione. In Visual Studio, c'è un'opzione sotto le preferenze per consentirgli di costruire più progetti in parallelo. Puoi anche utilizzare l' /MPopzione per il paralellismo a livello di file, anziché solo il paralellismo a livello di progetto.

Altre utilità parallele:

Utilizzare un livello di ottimizzazione inferiore

Più il compilatore cerca di ottimizzare, più deve lavorare duro.

Librerie condivise

Lo spostamento del codice modificato meno frequentemente nelle librerie può ridurre i tempi di compilazione. Utilizzando le librerie condivise ( .soo .dll), è possibile ridurre anche i tempi di collegamento.

Ottieni un computer più veloce

Più RAM, dischi rigidi più veloci (inclusi SSD) e più CPU / core fanno la differenza nella velocità di compilazione.


11
Le intestazioni precompilate non sono perfette però. Un effetto collaterale del loro utilizzo è che si ottengono più file del necessario (perché ogni unità di compilazione utilizza la stessa intestazione precompilata), che può forzare la ricompilazione completa più spesso del necessario. Solo qualcosa da tenere a mente.
jalf

8
Nei compilatori moderni, #ifndef è veloce quanto #pragma una volta (fintanto che la protezione include è nella parte superiore del file). Quindi non c'è alcun vantaggio per #pragma una volta in termini di velocità di compilazione
jalf

7
Anche se hai appena VS 2005, non 2008, puoi aggiungere l'opzione / MP nelle opzioni di compilazione per abilitare la costruzione parallela a livello .cpp.
macbirdie,

6
Gli SSD erano proibitivamente costosi quando è stata scritta questa risposta, ma oggi sono la scelta migliore durante la compilazione di C ++. Si accede a molti file di piccole dimensioni durante la compilazione ;. Ciò richiede molti IOPS, forniti dagli SSD.
Saluti

14
Preferisce il pass-by-reference al pass-by-value nelle firme di funzione. Ciò eliminerà la necessità di #includere le rispettive definizioni di tipo nel file di intestazione. Questo è sbagliato , non è necessario disporre del tipo completo per dichiarare una funzione che passa per valore, è necessario solo il tipo completo per implementare o utilizzare quella funzione , ma nella maggior parte dei casi (a meno che non si stia solo inoltrando le chiamate) sarà comunque necessaria tale definizione.
David Rodríguez - dribeas,

43

Lavoro sul progetto STAPL che è una libreria C ++ fortemente basata su modelli. Di tanto in tanto, dobbiamo rivisitare tutte le tecniche per ridurre i tempi di compilazione. Qui ho riassunto le tecniche che usiamo. Alcune di queste tecniche sono già elencate sopra:

Trovare le sezioni che richiedono più tempo

Sebbene non sia stata dimostrata una correlazione tra la lunghezza dei simboli e il tempo di compilazione, abbiamo osservato che dimensioni medie dei simboli più piccole possono migliorare il tempo di compilazione su tutti i compilatori. Quindi i tuoi primi obiettivi è quello di trovare i simboli più grandi nel tuo codice.

Metodo 1: ordina i simboli in base alle dimensioni

Puoi usare il nmcomando per elencare i simboli in base alle loro dimensioni:

nm --print-size --size-sort --radix=d YOUR_BINARY

In questo comando il --radix=d ti consente di vedere le dimensioni in numeri decimali (il valore predefinito è esadecimale). Ora osservando il simbolo più grande, identifica se puoi interrompere la classe corrispondente e prova a ridisegnarla fattorizzando le parti non modellate in una classe base o suddividendo la classe in più classi.

Metodo 2: ordina i simboli in base alla lunghezza

Puoi eseguire il nmcomando normale e reindirizzarlo al tuo script preferito ( AWK , Python , ecc.) Per ordinare i simboli in base alla loro lunghezza . Sulla base della nostra esperienza, questo metodo identifica il problema maggiore nel rendere i candidati migliori del metodo 1.

Metodo 3: utilizzare Templight

" Templight è un Clang strumento basato su il tempo e il consumo di memoria delle istanze dei modelli e per eseguire sessioni di debug interattive per ottenere introspezione nel processo di istanza dei modelli".

È possibile installare Templight controllando LLVM e Clang ( istruzioni ) e applicando la patch Templight su di esso. L'impostazione predefinita per LLVM e Clang è su debug e asserzioni e questi possono influire in modo significativo sui tempi di compilazione. Sembra che Templight abbia bisogno di entrambi, quindi devi usare le impostazioni predefinite. Il processo di installazione di LLVM e Clang dovrebbe durare circa un'ora.

Dopo aver applicato la patch è possibile utilizzare templight++ situato nella cartella build specificata al momento dell'installazione per compilare il codice.

Assicurati che templight++sia nel tuo PERCORSO. Ora per compilare aggiungi le seguenti opzioni al tuo CXXFLAGSnel tuo Makefile o alle opzioni della tua riga di comando:

CXXFLAGS+=-Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system

O

templight++ -Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system

Al termine della compilazione, avrai un .trace.memory.pbf e .trace.pbf generati nella stessa cartella. Per visualizzare queste tracce, è possibile utilizzare gli strumenti Templight che possono convertirli in altri formati. Segui queste istruzioni per installare templight-convert. Di solito usiamo l'output di callgrind. Puoi anche usare l'output di GraphViz se il tuo progetto è piccolo:

$ templight-convert --format callgrind YOUR_BINARY --output YOUR_BINARY.trace

$ templight-convert --format graphviz YOUR_BINARY --output YOUR_BINARY.dot

Il file callgrind generato può essere aperto usando kcachegrind in cui è possibile tracciare la maggiore istanza che richiede tempo / memoria.

Riduzione del numero di istanze del modello

Sebbene non esista una soluzione per ridurre il numero di istanze di modelli, esistono alcune linee guida che possono aiutare:

Classi refactor con più di un argomento template

Ad esempio, se hai una lezione,

template <typename T, typename U>
struct foo { };

ed entrambi Te Ucon 10 diverse opzioni, hai aumentato le possibili istanze di modello di questa classe a 100. Un modo per risolverlo è quello di astrarre la parte comune del codice in una classe diversa. L'altro metodo consiste nell'usare l'inversione dell'ereditarietà (invertendo la gerarchia di classi), ma assicurarsi che i propri obiettivi di progettazione non vengano compromessi prima di utilizzare questa tecnica.

Ricalcola il codice non basato su modelli in singole unità di traduzione

Usando questa tecnica, puoi compilare una volta la sezione comune e collegarla successivamente con le altre TU (unità di traduzione).

Usa le istanze di template esterne (dal C ++ 11)

Se conosci tutte le possibili istanze di una classe puoi usare questa tecnica per compilare tutti i casi in una diversa unità di traduzione.

Ad esempio, in:

enum class PossibleChoices = {Option1, Option2, Option3}

template <PossibleChoices pc>
struct foo { };

Sappiamo che questa classe può avere tre possibili istanze:

template class foo<PossibleChoices::Option1>;
template class foo<PossibleChoices::Option2>;
template class foo<PossibleChoices::Option3>;

Inserisci quanto sopra in un'unità di traduzione e utilizza la parola chiave extern nel file di intestazione, sotto la definizione della classe:

extern template class foo<PossibleChoices::Option1>;
extern template class foo<PossibleChoices::Option2>;
extern template class foo<PossibleChoices::Option3>;

Questa tecnica può farti risparmiare tempo se stai compilando diversi test con un insieme comune di istanze.

NOTA: MPICH2 ignora l'istanza esplicita a questo punto e compila sempre le classi istanziate in tutte le unità di compilazione.

Usa build di unità

L'idea alla base di unità build è quella di includere tutti i file .cc che usi in un file e compilarlo solo una volta. Usando questo metodo, puoi evitare di reintegrare sezioni comuni di file diversi e se il tuo progetto include molti file comuni, probabilmente risparmierai anche sugli accessi al disco.

Per fare un esempio, supponiamo di avere tre file foo1.cc, foo2.cc, foo3.cce sono tutte dotate tupledi STL . Puoi creare un foo-all.ccaspetto simile a:

#include "foo1.cc"
#include "foo2.cc"
#include "foo3.cc"

Compilare questo file solo una volta e potenzialmente ridurre le istanze comuni tra i tre file. È difficile prevedere in generale se il miglioramento può essere significativo o meno. Ma un fatto evidente è che perderai il parallelismo nelle tue build (non puoi più compilare i tre file contemporaneamente).

Inoltre, se uno di questi file richiede molta memoria, potresti effettivamente esaurire la memoria prima che la compilation sia terminata. Su alcuni compilatori, come GCC , questo potrebbe ICE (Internal Compiler Error) il compilatore per mancanza di memoria. Quindi non usare questa tecnica se non conosci tutti i pro e i contro.

Intestazioni precompilate

Le intestazioni precompilate (PCH) possono farti risparmiare molto tempo nella compilazione compilando i file di intestazione in una rappresentazione intermedia riconoscibile da un compilatore. Per generare file di intestazione precompilati, è sufficiente compilare il file di intestazione con il normale comando di compilazione. Ad esempio, su GCC:

$ g++ YOUR_HEADER.hpp

Questo genererà un YOUR_HEADER.hpp.gch file( .gchè l'estensione per i file PCH in GCC) nella stessa cartella. Ciò significa che se includi YOUR_HEADER.hppin qualche altro file, il compilatore utilizzerà prima il tuo YOUR_HEADER.hpp.gchinvece che YOUR_HEADER.hppnella stessa cartella.

Esistono due problemi con questa tecnica:

  1. Devi assicurarti che i file di intestazione precompilati siano stabili e non cambieranno ( puoi sempre cambiare il tuo makefile )
  2. Puoi includere solo un PCH per unità di compilazione (sulla maggior parte dei compilatori). Ciò significa che se si devono precompilare più file di intestazione, è necessario includerli in un file (ad es all-my-headers.hpp.). Ciò significa che devi includere il nuovo file in tutti i luoghi. Fortunatamente, GCC ha una soluzione per questo problema. Usa -includee dagli il nuovo file di intestazione. È possibile separare i diversi file con virgola utilizzando questa tecnica.

Per esempio:

g++ foo.cc -include all-my-headers.hpp

Usa spazi dei nomi anonimi o anonimi

Gli spazi dei nomi senza nome (noti anche come spazi dei nomi anonimi) possono ridurre significativamente le dimensioni binarie generate. Gli spazi dei nomi senza nome utilizzano un collegamento interno, il che significa che i simboli generati in tali spazi dei nomi non saranno visibili ad altre unità di traduzione (unità di traduzione o compilazione). I compilatori di solito generano nomi univoci per spazi dei nomi senza nome. Ciò significa che se si dispone di un file foo.hpp:

namespace {

template <typename T>
struct foo { };
} // Anonymous namespace
using A = foo<int>;

E ti capita di includere questo file in due TU (due file .cc e compilarli separatamente). Le due istanze di foo template non saranno le stesse. Ciò viola la One Definition Rule (ODR). Per lo stesso motivo, l'utilizzo di spazi dei nomi senza nome è sconsigliato nei file di intestazione. Sentiti libero di usarli nei tuoi .ccfile per evitare che i simboli vengano visualizzati nei tuoi file binari. In alcuni casi, la modifica di tutti i dettagli interni per un .ccfile ha mostrato una riduzione del 10% nelle dimensioni binarie generate.

Modifica delle opzioni di visibilità

Nei compilatori più recenti è possibile selezionare i simboli in modo che siano visibili o invisibili nei Dynamic Shared Objects (DSO). Idealmente, la modifica della visibilità può migliorare le prestazioni del compilatore, le ottimizzazioni del tempo di collegamento (LTO) e le dimensioni binarie generate. Se guardi i file di intestazione STL in GCC puoi vedere che è ampiamente usato. Per abilitare le opzioni di visibilità, è necessario modificare il codice per funzione, per classe, per variabile e, soprattutto, per compilatore.

Con l'aiuto della visibilità puoi nascondere i simboli che li consideri privati ​​dagli oggetti condivisi generati. Su GCC puoi controllare la visibilità dei simboli passando di default o nascosto -visibilityall'opzione del tuo compilatore. Questo è in qualche modo simile allo spazio dei nomi senza nome, ma in un modo più elaborato e invadente.

Se desideri specificare le visibilità per caso, devi aggiungere i seguenti attributi alle tue funzioni, variabili e classi:

__attribute__((visibility("default"))) void  foo1() { }
__attribute__((visibility("hidden")))  void  foo2() { }
__attribute__((visibility("hidden")))  class foo3   { };
void foo4() { }

La visibilità predefinita in GCC è predefinita (pubblica), il che significa che se si compila quanto sopra come -sharedmetodo di libreria condivisa ( ) foo2e la classe foo3non sarà visibile in altre TU ( foo1e foo4sarà visibile). Se lo compili con -visibility=hiddensolo allora foo1sarà visibile. Anche foo4sarebbe nascosto.

Puoi leggere di più sulla visibilità sul wiki di GCC .


33

Consiglierei questi articoli di "Giochi dall'interno, progettazione e programmazione di giochi indie":

Certo, sono piuttosto vecchi: dovrai testare nuovamente tutto con le ultime versioni (o versioni disponibili) per ottenere risultati realistici. Ad ogni modo, è una buona fonte di idee.


17

Una tecnica che ha funzionato abbastanza bene per me in passato: non compilare più file sorgente C ++ in modo indipendente, ma piuttosto generare un file C ++ che includa tutti gli altri file, come questo:

// myproject_all.cpp
// Automatically generated file - don't edit this by hand!
#include "main.cpp"
#include "mainwindow.cpp"
#include "filterdialog.cpp"
#include "database.cpp"

Ovviamente questo significa che devi ricompilare tutto il codice sorgente incluso nel caso in cui una delle fonti cambi, quindi l'albero delle dipendenze peggiora. Tuttavia, la compilazione di più file sorgente come un'unità di traduzione è più veloce (almeno nei miei esperimenti con MSVC e GCC) e genera binari più piccoli. Ho anche il sospetto che al compilatore sia dato più potenziale per le ottimizzazioni (dal momento che può vedere più codice contemporaneamente).

Questa tecnica si rompe in vari casi; ad esempio, il compilatore verrà salvato nel caso in cui due o più file di origine dichiarino una funzione globale con lo stesso nome. Non sono riuscito a trovare questa tecnica descritta in nessuna delle altre risposte, ecco perché la sto citando qui.

Per quello che vale, il Progetto KDE ha usato la stessa identica tecnica dal 1999 per creare binari ottimizzati (possibilmente per una versione). È stato chiamato il passaggio allo script di configurazione build --enable-final. Per interesse archeologico ho scoperto il post che annunciava questa funzione: http://lists.kde.org/?l=kde-devel&m=92722836009368&w=2


2
Non sono sicuro che sia davvero la stessa cosa, ma immagino che l'attivazione di "Ottimizzazione dell'intero programma" in VC ++ ( msdn.microsoft.com/en-us/library/0zza0de8%28VS.71%29.aspx ) dovrebbe avere stesso effetto sulle prestazioni di runtime rispetto a quello che stai suggerendo. Il tempo di compilazione, tuttavia, può sicuramente essere migliore nel tuo approccio!
Philipp,

1
@Frerich: stai descrivendo le build di Unity menzionate nella risposta di OJ. Li ho anche visti chiamati build di massa e build principali.
idbrii

Quindi, come si confronta un UB con WPO / LTCG?
paulm,

Ciò è potenzialmente utile solo per compilazioni singole, non durante lo sviluppo in cui si passa da modifica, creazione e test. Nel mondo moderno quattro core sono la norma, forse un paio di anni dopo il conteggio dei core è significativamente maggiore. Se il compilatore e il linker non sono in grado di utilizzare più thread, è possibile che l'elenco dei file possa essere suddiviso in <core-count> + Nelenchi compilati parallelamente in cui si Ntrova un numero intero adatto (a seconda della memoria di sistema e del modo in cui la macchina viene altrimenti utilizzata).
FooF,

15

C'è un intero libro su questo argomento, che si intitola Software di progettazione C ++ su larga scala (scritto da John Lakos).

Il libro precede i modelli, quindi al contenuto di quel libro aggiungi "anche l'uso dei modelli può rallentare il compilatore".


Il libro viene spesso citato in questo tipo di argomenti, ma per me era scarso nelle informazioni. In sostanza afferma di utilizzare le dichiarazioni forward il più possibile e di separare le dipendenze. Questo è un po 'affermando l'ovvio oltre che l'uso del linguaggio del pimpl ha degli svantaggi di runtime.
gast128,

@ gast128 Penso che il punto sia usare idiomi di codifica che consentono una ricompilazione incrementale, cioè, in modo che se cambi un po 'di sorgente da qualche parte non dovrai ricompilare tutto.
ChrisW,

15

Mi limiterò a collegarmi all'altra mia risposta: come ridurre il tempo di compilazione e il tempo di collegamento per i progetti Visual C ++ (C ++ nativo)? . Un altro punto che voglio aggiungere, ma che causa spesso problemi è l'uso di intestazioni precompilate. Ma per favore, usali solo per parti che non cambiano quasi mai (come le intestazioni del toolkit della GUI). Altrimenti, ti costeranno più tempo di quanto non ti risparmino alla fine.

Un'altra opzione è, quando lavori con GNU make, per attivare l' -j<N>opzione:

  -j [N], --jobs[=N]          Allow N jobs at once; infinite jobs with no arg.

Di solito ce l' 3ho da quando ho un dual core qui. Quindi eseguirà compilatori in parallelo per diverse unità di traduzione, a condizione che non vi siano dipendenze tra di loro. Il collegamento non può essere eseguito in parallelo, poiché esiste un solo processo di collegamento che collega insieme tutti i file oggetto.

Ma il linker stesso può essere threadato, ed è quello che fa il linker ELF . È un codice C ++ con thread ottimizzato che si dice che colleghi i file degli oggetti ELF molto più velocemente del vecchio (ed è stato effettivamente incluso in binutils ).GNU gold ld


Ok si. Mi dispiace che la domanda non sia emersa quando ho cercato.
Scott Langham,

non dovevi essere dispiaciuto. quello era per Visual c ++. la tua domanda sembra essere per qualsiasi compilatore. quindi va bene :)
Johannes Schaub - litb

12

Qui ce ne sono alcuni:

  • Utilizzare tutti i core del processore avviando un processo di compilazione multipla ( make -j2è un buon esempio).
  • Spegnere o abbassare le ottimizzazioni (ad esempio, GCC è molto più veloce con -O1più -O2o -O3).
  • Usa intestazioni precompilate .

12
Cordiali saluti, trovo che di solito sia più veloce avviare più processi rispetto ai core. Ad esempio su un sistema quad core, in genere utilizzo -j8, non -j4. La ragione di ciò è che quando un processo è bloccato su I / O, l'altro può essere compilato.
Fooz,

@MrFooz: l'ho provato qualche anno fa compilando il kernel Linux (dalla memoria RAM) su un i7-2700k (4 core, 8 thread, ho impostato un moltiplicatore costante). Dimentico il risultato esatto migliore, ma -j12in giro -j18sono stati notevolmente più veloci di -j8, proprio come suggerisci. Mi chiedo quanti core puoi avere prima che la larghezza di banda della memoria diventi il ​​fattore limitante ...
Mark K Cowan,

@MarkKCowan dipende da molti fattori. Computer diversi hanno larghezze di banda di memoria estremamente diverse. Al giorno d'oggi con processori di fascia alta, sono necessari più core per saturare il bus di memoria. Inoltre, c'è l'equilibrio tra I / O e CPU. Alcuni codici sono molto facili da compilare, altri possono essere lenti (ad es. Con molti modelli). La mia attuale regola empirica è il -jdoppio del numero di core effettivi.
Fooz,

11

Dopo aver applicato tutti i trucchi di codice sopra (dichiarazioni forward, riducendo al minimo l'inclusione delle intestazioni nelle intestazioni pubbliche, spingendo la maggior parte dei dettagli all'interno del file di implementazione con Pimpl ...) e nient'altro può essere acquisito dal punto di vista linguistico, considera il tuo sistema di compilazione . Se usi Linux, considera l'utilizzo di distcc (compilatore distribuito) e ccache (compilatore cache).

Il primo, distcc, esegue localmente il passo del preprocessore e quindi invia l'output al primo compilatore disponibile nella rete. Richiede le stesse versioni di compilatore e libreria in tutti i nodi configurati nella rete.

Quest'ultimo, ccache, è una cache del compilatore. Esegue nuovamente il preprocessore e quindi verifica con un database interno (conservato in una directory locale) se quel file del preprocessore è già stato compilato con gli stessi parametri del compilatore. In tal caso, visualizza semplicemente il file binario e l'output dalla prima esecuzione del compilatore.

Entrambi possono essere usati contemporaneamente, in modo che se ccache non ha una copia locale, può inviarlo attraverso la rete a un altro nodo con distcc, oppure può semplicemente iniettare la soluzione senza ulteriore elaborazione.


2
Non penso che distcc richieda le stesse versioni di libreria su tutti i nodi configurati. distcc esegue solo la compilazione in remoto, non il collegamento. Invia anche il codice preelaborato sul cavo, quindi le intestazioni disponibili sul sistema remoto non contano.
Frerich Raabe,

9

Quando sono uscito dal college, il primo vero codice C ++ degno di produzione che ho visto aveva queste arcane direttive #ifndef ... #endif tra loro dove sono state definite le intestazioni. Ho chiesto al ragazzo che stava scrivendo il codice su queste cose generali in modo molto ingenuo e mi è stato presentato il mondo della programmazione su larga scala.

Tornando al punto, usare le direttive per prevenire duplicate delle definizioni delle intestazioni è stata la prima cosa che ho imparato quando si trattava di ridurre i tempi di compilazione.


1
vecchio ma buono. a volte l'ovvio è dimenticato.
Alcor,

1
'include guards'
gast128,

8

Più RAM.

Qualcuno ha parlato di unità RAM in un'altra risposta. L'ho fatto con un 80286 e Turbo C ++ (mostra l'età) e i risultati sono stati fenomenali. Così come la perdita di dati in caso di crash della macchina.


in DOS non puoi avere molta memoria però
phuclv il

6

Usa dichiarazioni in avanti dove puoi. Se una dichiarazione di classe utilizza solo un puntatore o un riferimento a un tipo, puoi semplicemente dichiararla in avanti e includere l'intestazione per il tipo nel file di implementazione.

Per esempio:

// T.h
class Class2; // Forward declaration

class T {
public:
    void doSomething(Class2 &c2);
private:
    Class2 *m_Class2Ptr;
};

// T.cpp
#include "Class2.h"
void Class2::doSomething(Class2 &c2) {
    // Whatever you want here
}

Meno include significa molto meno lavoro per il preprocessore se lo fai abbastanza.


Non importa solo quando la stessa intestazione è inclusa in diverse unità di traduzione? Se esiste una sola unità di traduzione (come spesso accade quando si utilizzano i modelli), ciò sembrerebbe non avere alcun impatto.
AlwaysLearning

1
Se esiste una sola unità di traduzione, perché preoccuparsi di inserirlo in un'intestazione? Non avrebbe più senso mettere il contenuto nel file sorgente? Il punto cruciale delle intestazioni non è che è probabile che sia incluso da più di un file sorgente?
Evan Teran,


5

Uso

#pragma once

nella parte superiore dei file di intestazione, quindi se vengono inclusi più di una volta in un'unità di traduzione, il testo dell'intestazione verrà incluso e analizzato una sola volta.


2
Sebbene ampiamente supportato, #pragma una volta non è standard. Vedi en.wikipedia.org/wiki/Pragma_once
ChrisInEdmonton

7
E in questi giorni, le guardie include regolari hanno lo stesso effetto. Finché sono nella parte superiore del file, il compilatore è in grado di trattarli come #pragma una volta
jalf


4
  • Aggiorna il tuo computer

    1. Ottieni un quad core (o un sistema dual-quad)
    2. Ottieni un sacco di RAM.
    3. Utilizzare un'unità RAM per ridurre drasticamente i ritardi di I / O dei file. (Ci sono aziende che producono unità RAM IDE e SATA che si comportano come dischi rigidi).
  • Quindi hai tutti gli altri suggerimenti tipici

    1. Usa le intestazioni precompilate, se disponibili.
    2. Ridurre la quantità di accoppiamento tra le parti del progetto. La modifica di un file di intestazione di solito non dovrebbe richiedere la ricompilazione dell'intero progetto.

4

Ho avuto un'idea sull'utilizzo di un'unità RAM . Si è scoperto che, dopo tutto, per i miei progetti non fa molta differenza. Ma poi sono ancora abbastanza piccoli. Provalo! Sarei interessato a sapere quanto mi ha aiutato.


Huh. Perché qualcuno ha votato in negativo? Lo proverò domani.
Scott Langham,

1
Mi aspetto che il downvote sia perché non fa mai una grande differenza. Se si dispone di RAM inutilizzata sufficiente, il sistema operativo la utilizzerà comunque in modo intelligente come cache del disco.
Saluti

1
@MSalters - e quanto sarebbe "sufficiente"? So che questa è la teoria, ma per qualche ragione si utilizza un RAMDrive non effettivamente dare un notevole impulso. Vai a capire ...
Vilx-

1
abbastanza per compilare il progetto e ancora memorizzare nella cache i file di input e temporanei. Ovviamente il lato in GB dipenderà direttamente dalle dimensioni del tuo progetto. Va notato che sui vecchi sistemi operativi (WinXP in particolare) le cache dei file erano piuttosto pigre, lasciando la RAM inutilizzata.
Saluti

Sicuramente il disco RAM è più veloce se i file sono già in RAM anziché fare prima un sacco di IO lenti, quindi sono in RAM? (aumento-ripetizione per i file che sono stati modificati: riscrivili su disco, ecc.).
paulm

3

Il collegamento dinamico (.so) può essere molto più veloce del collegamento statico (.a). Soprattutto quando si dispone di un'unità di rete lenta. Questo perché hai tutto il codice nel file .a che deve essere elaborato e scritto. Inoltre, è necessario scrivere sul disco un file eseguibile molto più grande.


il collegamento dinamico impedisce molti tipi di ottimizzazioni del tempo di collegamento, quindi l'output potrebbe essere più lento in molti casi
phuclv,

3

Non sul tempo di compilazione, ma sul tempo di compilazione:

  • Usa ccache se devi ricostruire gli stessi file quando lavori sui tuoi buildfile

  • Usa ninja-build invece di make. Attualmente sto compilando un progetto con ~ 100 file sorgente e tutto è memorizzato nella cache da ccache. rendere necessari 5 minuti, ninja meno di 1.

Puoi generare i tuoi file ninja da cmake con -GNinja.


3

Dove trascorri il tuo tempo? Sei associato alla CPU? Memoria legata? Disco associato? Puoi usare più core? Più RAM? Hai bisogno di RAID? Vuoi semplicemente migliorare l'efficienza del tuo attuale sistema?

Sotto gcc / g ++, hai guardato ccache ? Può essere utile se stai facendo make clean; makemolto.


2

Dischi rigidi più veloci.

I compilatori scrivono molti (e forse enormi) file su disco. Lavora con SSD invece del tipico disco rigido e i tempi di compilazione sono molto più bassi.



2

Le condivisioni di rete rallenteranno drasticamente la tua build, poiché la latenza di ricerca è elevata. Per qualcosa come Boost, ha fatto un'enorme differenza per me, anche se il nostro disco di condivisione di rete è piuttosto veloce. Il tempo di compilare un programma Boost giocattolo è passato da circa 1 minuto a 1 secondo quando sono passato da una condivisione di rete a un SSD locale.


2

Se si dispone di un processore multicore, sia Visual Studio (2005 e versioni successive) sia GCC supportano compilazioni multiprocessore. È qualcosa da abilitare se hai l'hardware, di sicuro.


2
@Fellman, guarda alcune delle altre risposte - usa l'opzione -j #.
Strager

1

Sebbene non sia una "tecnica", non sono riuscito a capire come i progetti Win32 con molti file sorgente siano stati compilati più velocemente del mio progetto vuoto "Hello World". Quindi, spero che questo aiuti qualcuno come me.

In Visual Studio, un'opzione per aumentare i tempi di compilazione è il collegamento incrementale ( / INCREMENTAL ). È incompatibile con Link-time Code Generation ( / LTCG ), quindi ricorda di disabilitare il collegamento incrementale quando esegui build di rilascio.


1
disabilitare la generazione del codice Link-time non è un buon suggerimento in quanto disabilita molte ottimizzazioni. È necessario abilitare solo /INCREMENTALin modalità debug
phuclv

1

A partire da Visual Studio 2017 hai la possibilità di avere alcune metriche del compilatore su ciò che richiede tempo.

Aggiungi quei parametri a C / C ++ -> Riga di comando (Opzioni aggiuntive) nella finestra delle proprietà del progetto: /Bt+ /d2cgsummary /d1reportTime

Puoi avere maggiori informazioni in questo post .


0

L'uso del collegamento dinamico anziché statico ti rende il compilatore più veloce che puoi sentire.

Se usi t Cmake, attiva la proprietà:

set(BUILD_SHARED_LIBS ON)

Build Release, utilizzando il collegamento statico può ottenere più ottimizzazione.

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.