Quelles techniques peuvent être utilisées pour accélérer les temps de compilation C ++?
Cette question a été soulevée dans certains commentaires sur le style de programmation de la question C ++ de Stack Overflow , et je suis intéressé d'entendre quelles idées il y a.
J'ai vu une question connexe, pourquoi la compilation C ++ prend-elle autant de temps? , mais cela ne fournit pas beaucoup de solutions.
Réponses:
Techniques langagières
Pimpl Idiom
Jetez un œil à l' idiome Pimpl ici et ici , également connu sous le nom de pointeur opaque ou de classes de descripteurs . Non seulement il accélère la compilation, mais il augmente également la sécurité des exceptions lorsqu'il est combiné avec une fonction d' échange sans lancement . L'idiome Pimpl vous permet de réduire les dépendances entre les en-têtes et réduit la quantité de recompilation à effectuer.
Déclarations à terme
Dans la mesure du possible, utilisez des déclarations à terme . Si le compilateur a seulement besoin de savoir qu'il
SomeIdentifier
s'agit d'une structure ou d'un pointeur ou autre, n'incluez pas la définition entière, forçant le compilateur à faire plus de travail qu'il n'en a besoin. Cela peut avoir un effet en cascade, ce qui rend cette façon plus lente que nécessaire.Les flux d' E / S sont particulièrement connus pour ralentir les builds. Si vous en avez besoin dans un fichier d'en-tête, essayez #including
<iosfwd>
au lieu de<iostream>
et #include l'en-<iostream>
tête dans le fichier d'implémentation uniquement. L'en-<iosfwd>
tête contient uniquement les déclarations avancées. Malheureusement, les autres en-têtes standard n'ont pas d'en-tête de déclaration respectif.Préférez le passage par référence au passage par valeur dans les signatures de fonction. Cela éliminera le besoin d'inclure les définitions de type respectives dans le fichier d'en-tête et vous n'aurez qu'à déclarer le type en amont. Bien sûr, préférez les références const aux références non const pour éviter les bugs obscurs, mais c'est un problème pour une autre question.
Conditions de garde
Utilisez des conditions de garde pour empêcher les fichiers d'en-tête d'être inclus plusieurs fois dans une seule unité de traduction.
En utilisant à la fois le pragma et l'ifndef, vous obtenez la portabilité de la solution de macro simple, ainsi que l'optimisation de la vitesse de compilation que certains compilateurs peuvent faire en présence de la
pragma once
directive.Réduisez l'interdépendance
Plus votre conception de code est modulaire et moins interdépendante en général, moins vous devrez tout recompiler. Vous pouvez également finir par réduire la quantité de travail que le compilateur doit faire sur n'importe quel bloc individuel en même temps, en raison du fait qu'il a moins à suivre.
Options du compilateur
En-têtes précompilés
Ils sont utilisés pour compiler une section commune des en-têtes inclus une fois pour de nombreuses unités de traduction. Le compilateur le compile une fois et enregistre son état interne. Cet état peut ensuite être chargé rapidement pour obtenir une longueur d'avance dans la compilation d'un autre fichier avec ce même ensemble d'en-têtes.
Veillez à n'inclure que des éléments rarement modifiés dans les en-têtes précompilés, ou vous pourriez finir par effectuer des reconstructions complètes plus souvent que nécessaire. C'est un bon endroit pour les en- têtes STL et les autres fichiers d'inclusion de bibliothèque.
ccache est un autre utilitaire qui tire parti des techniques de mise en cache pour accélérer les choses.
Utiliser le parallélisme
De nombreux compilateurs / IDE prennent en charge l'utilisation de plusieurs cœurs / CPU pour effectuer la compilation simultanément. Dans GNU Make (généralement utilisé avec GCC), utilisez l'
-j [N]
option. Dans Visual Studio, il y a une option sous les préférences pour lui permettre de construire plusieurs projets en parallèle. Vous pouvez également utiliser l'/MP
option pour le paralellisme au niveau du fichier, au lieu du seul paralellisme au niveau du projet.Autres utilitaires parallèles:
Utiliser un niveau d'optimisation inférieur
Plus le compilateur essaie d'optimiser, plus il doit travailler dur.
Bibliothèques partagées
Déplacer votre code moins fréquemment modifié dans des bibliothèques peut réduire le temps de compilation. En utilisant des bibliothèques partagées (
.so
ou.dll
), vous pouvez également réduire le temps de liaison.Obtenez un ordinateur plus rapide
Plus de RAM, des disques durs plus rapides (y compris les SSD) et plus de CPU / cœurs feront tous une différence dans la vitesse de compilation.
la source
Je travaille sur le projet STAPL qui est une bibliothèque C ++ fortement basée sur des modèles. De temps en temps, nous devons revoir toutes les techniques pour réduire le temps de compilation. Ici, j'ai résumé les techniques que nous utilisons. Certaines de ces techniques sont déjà répertoriées ci-dessus:
Trouver les sections les plus chronophages
Bien qu'il n'y ait pas de corrélation prouvée entre les longueurs de symboles et le temps de compilation, nous avons observé que des tailles moyennes de symboles plus petites peuvent améliorer le temps de compilation sur tous les compilateurs. Donc, votre premier objectif est de trouver les plus grands symboles dans votre code.
Méthode 1 - Trier les symboles en fonction de la taille
Vous pouvez utiliser la
nm
commande pour répertorier les symboles en fonction de leur taille:Dans cette commande, le
--radix=d
vous permet de voir les tailles en nombres décimaux (la valeur par défaut est hexadécimale). Maintenant, en regardant le plus grand symbole, identifiez si vous pouvez séparer la classe correspondante et essayez de la repenser en factorisant les parties non basées sur un modèle dans une classe de base, ou en divisant la classe en plusieurs classes.Méthode 2 - Trier les symboles en fonction de la longueur
Vous pouvez exécuter la
nm
commande standard et la diriger vers votre script préféré ( AWK , Python , etc.) pour trier les symboles en fonction de leur longueur . D'après notre expérience, cette méthode identifie les problèmes les plus importants pour rendre les candidats meilleurs que la méthode 1.Méthode 3 - Utilisez Templight
" Templight est un outil basé sur Clang pour profiler la consommation de temps et de mémoire des instanciations de modèles et pour effectuer des sessions de débogage interactives afin de gagner en introspection dans le processus d'instanciation de modèles".
Vous pouvez installer Templight en vérifiant LLVM et Clang ( instructions ) et en y appliquant le correctif Templight. Le paramètre par défaut pour LLVM et Clang concerne le débogage et les assertions, et ceux-ci peuvent avoir un impact significatif sur votre temps de compilation. Il semble que Templight ait besoin des deux, vous devez donc utiliser les paramètres par défaut. Le processus d'installation de LLVM et Clang devrait prendre environ une heure.
Après avoir appliqué le correctif, vous pouvez utiliser
templight++
situé dans le dossier de construction que vous avez spécifié lors de l'installation pour compiler votre code.Assurez-vous que cela se
templight++
trouve dans votre CHEMIN. Maintenant, pour compiler, ajoutez les commutateurs suivants à votreCXXFLAGS
dans votre Makefile ou à vos options de ligne de commande:Ou
Une fois la compilation terminée, vous aurez un .trace.memory.pbf et .trace.pbf générés dans le même dossier. Pour visualiser ces traces, vous pouvez utiliser les outils Templight qui peuvent les convertir en d'autres formats. Suivez ces instructions pour installer templight-convert. Nous utilisons généralement la sortie callgrind. Vous pouvez également utiliser la sortie GraphViz si votre projet est petit:
Le fichier callgrind généré peut être ouvert à l'aide de kcachegrind dans lequel vous pouvez tracer l'instanciation la plus consommatrice de temps / mémoire.
Réduction du nombre d'instanciations de modèles
Bien qu'il n'y ait pas de solution exacte pour réduire le nombre d'instanciations de modèles, il existe quelques directives qui peuvent vous aider:
Classes de refactorisation avec plusieurs arguments de modèle
Par exemple, si vous avez une classe,
et à la fois
T
etU
peuvent avoir 10 options différentes, vous avez augmenté les instanciations de modèle possibles de cette classe à 100. Une façon de résoudre ce problème est d'abstraire la partie commune du code à une classe différente. L'autre méthode consiste à utiliser l'inversion d'héritage (inversion de la hiérarchie des classes), mais assurez-vous que vos objectifs de conception ne sont pas compromis avant d'utiliser cette technique.Refactoriser le code non modélisé en unités de traduction individuelles
En utilisant cette technique, vous pouvez compiler une fois la section commune et la lier à vos autres UT (unités de traduction) ultérieurement.
Utiliser des instanciations de modèles externes (depuis C ++ 11)
Si vous connaissez toutes les instanciations possibles d'une classe, vous pouvez utiliser cette technique pour compiler tous les cas dans une unité de traduction différente.
Par exemple, dans:
Nous savons que cette classe peut avoir trois instanciations possibles:
Mettez ce qui précède dans une unité de traduction et utilisez le mot clé extern dans votre fichier d'en-tête, sous la définition de classe:
Cette technique peut vous faire gagner du temps si vous compilez différents tests avec un ensemble commun d'instanciations.
Utiliser des builds d'unité
L'idée derrière les builds d'unité est d'inclure tous les fichiers .cc que vous utilisez dans un seul fichier et de compiler ce fichier une seule fois. En utilisant cette méthode, vous pouvez éviter de réinstaurer des sections communes de différents fichiers et si votre projet comprend un grand nombre de fichiers communs, vous économiserez probablement également sur les accès au disque.
À titre d'exemple, supposons que vous avez trois fichiers
foo1.cc
,foo2.cc
,foo3.cc
et ils comprennent toustuple
de STL . Vous pouvez créer unfoo-all.cc
qui ressemble à:Vous compilez ce fichier une seule fois et réduisez potentiellement les instanciations courantes entre les trois fichiers. Il est généralement difficile de prédire si l'amélioration peut être significative ou non. Mais un fait évident est que vous perdriez le parallélisme dans vos builds (vous ne pouvez plus compiler les trois fichiers en même temps).
De plus, si l'un de ces fichiers nécessite beaucoup de mémoire, vous risquez de manquer de mémoire avant la fin de la compilation. Sur certains compilateurs, tels que GCC , cela pourrait provoquer une erreur ICE (Internal Compiler Error) sur votre compilateur par manque de mémoire. N'utilisez donc pas cette technique à moins de connaître tous les avantages et les inconvénients.
En-têtes précompilés
Les en-têtes précompilés (PCH) peuvent vous faire gagner beaucoup de temps lors de la compilation en compilant vos fichiers d'en-tête dans une représentation intermédiaire reconnaissable par un compilateur. Pour générer des fichiers d'en-tête précompilés, il vous suffit de compiler votre fichier d'en-tête avec votre commande de compilation régulière. Par exemple, sur GCC:
Cela générera un
YOUR_HEADER.hpp.gch file
(.gch
est l'extension pour les fichiers PCH dans GCC) dans le même dossier. Cela signifie que si vous incluezYOUR_HEADER.hpp
dans un autre fichier, le compilateur utilisera votreYOUR_HEADER.hpp.gch
au lieu deYOUR_HEADER.hpp
dans le même dossier auparavant.Il y a deux problèmes avec cette technique:
all-my-headers.hpp
). Mais cela signifie que vous devez inclure le nouveau fichier à tous les endroits. Heureusement, GCC a une solution à ce problème. Utilisez-include
et donnez-lui le nouveau fichier d'en-tête. Vous pouvez séparer par virgule différents fichiers à l'aide de cette technique.Par exemple:
Utiliser des espaces de noms sans nom ou anonymes
Les espaces de noms sans nom (ou espaces de noms anonymes) peuvent réduire considérablement les tailles binaires générées. Les espaces de noms sans nom utilisent une liaison interne, ce qui signifie que les symboles générés dans ces espaces de noms ne seront pas visibles par les autres UT (unités de traduction ou de compilation). Les compilateurs génèrent généralement des noms uniques pour les espaces de noms sans nom. Cela signifie que si vous avez un fichier foo.hpp:
Et il se trouve que vous incluez ce fichier dans deux UT (deux fichiers .cc et les compilez séparément). Les deux instances de modèle foo ne seront pas identiques. Cela viole la règle de définition unique (ODR). Pour la même raison, l'utilisation d'espaces de noms sans nom est déconseillée dans les fichiers d'en-tête. N'hésitez pas à les utiliser dans vos
.cc
fichiers pour éviter que des symboles n'apparaissent dans vos fichiers binaires. Dans certains cas, la modification de tous les détails internes d'un.cc
fichier a montré une réduction de 10% des tailles binaires générées.Modification des options de visibilité
Dans les nouveaux compilateurs, vous pouvez sélectionner vos symboles pour qu'ils soient visibles ou invisibles dans les objets partagés dynamiques (DSO). Idéalement, la modification de la visibilité peut améliorer les performances du compilateur, les optimisations de temps de liaison (LTO) et les tailles binaires générées. Si vous regardez les fichiers d'en-tête STL dans GCC, vous pouvez voir qu'il est largement utilisé. Pour activer les choix de visibilité, vous devez modifier votre code par fonction, par classe, par variable et, plus important encore, par compilateur.
Avec l'aide de la visibilité, vous pouvez masquer les symboles que vous considérez comme privés des objets partagés générés. Sur GCC, vous pouvez contrôler la visibilité des symboles en passant par défaut ou masqué à l'
-visibility
option de votre compilateur. Ceci est en quelque sorte similaire à l'espace de noms sans nom, mais d'une manière plus élaborée et intrusive.Si vous souhaitez spécifier les visibilités par cas, vous devez ajouter les attributs suivants à vos fonctions, variables et classes:
La visibilité par défaut dans GCC est default (public), ce qui signifie que si vous compilez ce qui précède en tant que
-shared
méthode library ( ) partagée ,foo2
et que la classefoo3
ne sera pas visible dans les autres TU (foo1
etfoo4
sera visible). Si vous compilez avec-visibility=hidden
alors seulementfoo1
sera visible. Mêmefoo4
serait caché.Vous pouvez en savoir plus sur la visibilité sur le wiki de GCC .
la source
Je recommanderais ces articles de "Games from Within, Indie Game Design And Programming":
Certes, ils sont assez vieux - vous devrez tout tester à nouveau avec les dernières versions (ou les versions disponibles), pour obtenir des résultats réalistes. Quoi qu'il en soit, c'est une bonne source d'idées.
la source
Une technique qui a très bien fonctionné pour moi dans le passé: ne compilez pas plusieurs fichiers source C ++ indépendamment, mais générez plutôt un fichier C ++ qui inclut tous les autres fichiers, comme ceci:
Bien sûr, cela signifie que vous devez recompiler tout le code source inclus au cas où l'une des sources changerait, de sorte que l'arbre de dépendance s'aggrave. Cependant, la compilation de plusieurs fichiers source en une seule unité de traduction est plus rapide (au moins dans mes expériences avec MSVC et GCC) et génère des binaires plus petits. Je soupçonne également que le compilateur a plus de potentiel d'optimisation (car il peut voir plus de code à la fois).
Cette technique casse dans divers cas; par exemple, le compilateur se renflouera au cas où deux fichiers source ou plus déclarent une fonction globale avec le même nom. Je n'ai pas trouvé cette technique décrite dans aucune des autres réponses, c'est pourquoi je la mentionne ici.
Pour ce que ça vaut, le projet KDE a utilisé cette même technique depuis 1999 pour construire des binaires optimisés (éventuellement pour une version). Le basculement vers le script de configuration de build a été appelé
--enable-final
. Par intérêt archéologique, j'ai déterré l'affichage qui annonçait cette fonctionnalité: http://lists.kde.org/?l=kde-devel&m=92722836009368&w=2la source
<core-count> + N
sous-listes qui sont compilées en parallèle où seN
trouve un entier approprié (en fonction de la mémoire système et de la façon dont la machine est utilisée autrement).Il y a un livre entier sur ce sujet, qui s'intitule Large-Scale C ++ Software Design (écrit par John Lakos).
Le livre précède les modèles, donc au contenu de ce livre, ajoutez «l'utilisation de modèles peut également ralentir le compilateur».
la source
Je vais simplement créer un lien vers mon autre réponse: comment réduisez-vous le temps de compilation et le temps de liaison pour les projets Visual C ++ (C ++ natif)? . Un autre point que je veux ajouter, mais qui pose souvent des problèmes, est d'utiliser des en-têtes précompilés. Mais s'il vous plaît, utilisez-les uniquement pour les pièces qui ne changent presque jamais (comme les en-têtes de la boîte à outils GUI). Sinon, ils vous coûteront plus de temps qu'ils ne vous feront économiser au final.
Une autre option est, lorsque vous travaillez avec GNU make, d'activer l'
-j<N>
option:Je l'ai généralement
3
depuis que j'ai un dual core ici. Il exécutera ensuite des compilateurs en parallèle pour différentes unités de traduction, à condition qu'il n'y ait pas de dépendances entre eux. La liaison ne peut pas être effectuée en parallèle, car il n'existe qu'un seul processus de liaison liant tous les fichiers objets.Mais l'éditeur de liens lui-même peut être enfilé, et c'est ce que fait l' éditeur de liens ELF . Il s'agit d'un code C ++ threadé optimisé qui est censé lier les fichiers objets ELF une amplitude plus rapide que l'ancien (et qui était en fait inclus dans binutils ).
GNU gold
ld
la source
Voilà quelque:
make -j2
c'est un bon exemple).-O1
que-O2
ou-O3
).la source
-j12
à environ-j18
étaient beaucoup plus vite que-j8
, comme vous le suggérez. Je me demande combien de cœurs vous pouvez avoir avant que la bande passante mémoire ne devienne le facteur limitant ...-j
avec 2x le nombre de cœurs réels.Une fois que vous avez appliqué toutes les astuces de code ci-dessus (déclarations avancées, réduire l'inclusion d'en-tête au minimum dans les en-têtes publics, pousser la plupart des détails dans le fichier d'implémentation avec Pimpl ...) et que rien d'autre ne peut être obtenu en termes de langue, pensez à votre système de construction . Si vous utilisez Linux, pensez à utiliser distcc (compilateur distribué) et ccache (compilateur de cache).
Le premier, distcc, exécute l'étape du préprocesseur localement, puis envoie la sortie au premier compilateur disponible du réseau. Il nécessite les mêmes versions de compilateur et de bibliothèque dans tous les nœuds configurés du réseau.
Ce dernier, ccache, est un cache de compilateur. Il exécute à nouveau le préprocesseur, puis vérifie auprès d'une base de données interne (conservée dans un répertoire local) si ce fichier de préprocesseur a déjà été compilé avec les mêmes paramètres de compilateur. Si c'est le cas, il affiche simplement le binaire et la sortie de la première exécution du compilateur.
Les deux peuvent être utilisés en même temps, de sorte que si ccache n'a pas de copie locale, il peut l'envoyer via le net à un autre nœud avec distcc, ou bien il peut simplement injecter la solution sans traitement supplémentaire.
la source
Quand je suis sorti de l'université, le premier vrai code C ++ digne de production que j'ai vu avait ces directives arcanes #ifndef ... #endif entre elles où les en-têtes étaient définis. J'ai demandé au gars qui écrivait le code de ces choses primordiales d'une manière très naïve et a été initié au monde de la programmation à grande échelle.
Pour en revenir au fait, l'utilisation de directives pour éviter les définitions d'en-tête en double a été la première chose que j'ai apprise en matière de réduction des temps de compilation.
la source
Plus de RAM.
Quelqu'un a parlé des lecteurs RAM dans une autre réponse. Je l'ai fait avec un 80286 et un Turbo C ++ (montre l'âge) et les résultats ont été phénoménaux. Tout comme la perte de données lorsque la machine s'est écrasée.
la source
Utilisez des déclarations avancées lorsque vous le pouvez. Si une déclaration de classe utilise uniquement un pointeur ou une référence à un type, vous pouvez simplement le déclarer et inclure l'en-tête du type dans le fichier d'implémentation.
Par exemple:
Moins d'inclure signifie beaucoup moins de travail pour le préprocesseur si vous le faites suffisamment.
la source
Vous pouvez utiliser Unity Builds .
La
la source
Utilisation
en haut des fichiers d'en-tête, donc s'ils sont inclus plus d'une fois dans une unité de traduction, le texte de l'en-tête ne sera inclus et analysé qu'une seule fois.
la source
Juste pour être complet: une construction peut être lente parce que le système de construction est stupide ainsi que parce que le compilateur prend beaucoup de temps pour faire son travail.
Lisez Récursif Rendre considéré comme nuisible (PDF) pour une discussion de ce sujet dans les environnements Unix.
la source
Mettez à niveau votre ordinateur
Ensuite, vous avez toutes vos autres suggestions typiques
la source
J'ai eu une idée sur l' utilisation d'un lecteur RAM . Il s'est avéré que pour mes projets, cela ne fait pas beaucoup de différence après tout. Mais alors ils sont encore assez petits. Essayez! J'aimerais savoir à quel point cela a aidé.
la source
La liaison dynamique (.so) peut être beaucoup plus rapide que la liaison statique (.a). Surtout lorsque vous avez un lecteur réseau lent. C'est parce que vous avez tout le code dans le fichier .a qui doit être traité et écrit. De plus, un fichier exécutable beaucoup plus volumineux doit être écrit sur le disque.
la source
Pas sur le temps de compilation, mais sur le temps de construction:
Utilisez ccache si vous devez reconstruire les mêmes fichiers lorsque vous travaillez sur vos fichiers de build
Utilisation ninja-build au lieu de make. Je compile actuellement un projet avec ~ 100 fichiers sources et tout est mis en cache par ccache. faire des besoins 5 minutes, ninja moins de 1.
Vous pouvez générer vos fichiers ninja à partir de cmake avec
-GNinja
.la source
Où passez-vous votre temps? Êtes-vous lié au processeur? Mémoire liée? Disque lié? Pouvez-vous utiliser plus de cœurs? Plus de RAM? Avez-vous besoin d'un RAID? Voulez-vous simplement améliorer l'efficacité de votre système actuel?
Sous gcc / g ++, avez-vous regardé ccache ? Cela peut être utile si vous en faites
make clean; make
beaucoup.la source
Disques durs plus rapides.
Les compilateurs écrivent de nombreux (et peut-être d'énormes) fichiers sur le disque. Travaillez avec SSD au lieu du disque dur typique et les temps de compilation sont beaucoup plus courts.
la source
Sous Linux (et peut-être d'autres * NIX), vous pouvez vraiment accélérer la compilation en NE DÉMARRANT PAS à la sortie et en changeant pour un autre TTY.
Voici l'expérience: printf ralentit mon programme
la source
Les partages réseaux ralentiront considérablement votre build, car la latence de recherche est élevée. Pour quelque chose comme Boost, cela a fait une énorme différence pour moi, même si notre lecteur de partage réseau est assez rapide. Le temps de compilation d'un programme Boost jouet est passé d'environ 1 minute à 1 seconde lorsque je suis passé d'un partage réseau à un SSD local.
la source
Si vous avez un processeur multicœur, Visual Studio (2005 et versions ultérieures) ainsi que GCC prennent en charge les compilations multiprocesseurs. C'est quelque chose à activer si vous avez le matériel, c'est sûr.
la source
Bien que ce ne soit pas une "technique", je n'ai pas pu comprendre comment les projets Win32 avec de nombreux fichiers source compilés plus rapidement que mon projet vide "Hello World". Ainsi, j'espère que cela aide quelqu'un comme moi.
Dans Visual Studio, une option pour augmenter les temps de compilation est la liaison incrémentielle ( / INCREMENTAL ). Il est incompatible avec la génération de code au moment de la liaison ( / LTCG ), alors n'oubliez pas de désactiver la liaison incrémentielle lors des générations de versions.
la source
/INCREMENTAL
en mode débogage uniquementÀ partir de Visual Studio 2017, vous avez la possibilité d'avoir des mesures de compilation sur ce qui prend du temps.
Ajoutez ces paramètres à C / C ++ -> Ligne de commande (Options supplémentaires) dans la fenêtre des propriétés du projet:
/Bt+ /d2cgsummary /d1reportTime
Vous pouvez avoir plus d'informations dans cet article .
la source
L'utilisation d'une liaison dynamique au lieu d'une liaison statique rend votre compilateur plus rapide que vous pouvez ressentir.
Si vous utilisez t Cmake, activez la propriété:
Build Release, l'utilisation de liens statiques peut être plus optimisée.
la source