Question Pourquoi la compilation C ++ prend-elle autant de temps?


Compiler un fichier C ++ prend beaucoup de temps comparé à C # et Java. Il faut beaucoup plus de temps pour compiler un fichier C ++ que pour exécuter un script Python de taille normale. J'utilise actuellement VC ++ mais c'est la même chose avec n'importe quel compilateur. Pourquoi est-ce?

Les deux raisons que je pouvais penser étaient le chargement des fichiers d'en-tête et l'exécution du préprocesseur, mais cela ne semble pas devoir expliquer pourquoi cela prend tant de temps.


453
2017-11-25 18:25


origine


Réponses:


Plusieurs raisons:

  • Fichiers d'en-tête: Chaque unité de compilation nécessite des centaines ou même des milliers d'en-têtes 1: chargé et 2: compilé. Chacun d'entre eux doit généralement être recompilé pour chaque unité de compilation, car le préprocesseur s'assure que le résultat de la compilation d'un en-tête pourrait varient entre chaque unité de compilation. (Une macro peut être définie dans une unité de compilation qui modifie le contenu de l'en-tête).

    C'est probablement la raison principale, car il faut d'énormes quantités de code à compiler pour chaque unité de compilation, et en plus, chaque en-tête doit être compilé plusieurs fois (une fois pour chaque unité de compilation qui l'inclut)

  • Mise en relation: Une fois compilés, tous les fichiers objets doivent être liés entre eux. C'est fondamentalement un processus monolithique qui ne peut pas très bien être parallélisé, et doit traiter votre projet entier.

  • Analyse syntaxique La syntaxe est extrêmement compliquée à analyser, dépend fortement du contexte et est très difficile à désamorcer. Cela prend beaucoup de temps

  • Modèles: En C #, List<T> est le seul type compilé, quel que soit le nombre d'instanciations de List que vous avez dans votre programme. En C ++, vector<int> est un type complètement séparé de vector<float>, et chacun devra être compilé séparément.

    Ajoutez à cela que les templates constituent un "sous-langage" complet que le compilateur doit interpréter, ce qui peut devenir ridiculement compliqué. Même le code de métaprogrammation de modèle relativement simple peut définir des modèles récursifs qui créent des douzaines et des douzaines d'instanciations de modèles. Les modèles peuvent également aboutir à des types extrêmement complexes, avec des noms ridiculement longs, ajoutant beaucoup de travail supplémentaire à l'éditeur de liens. (Il doit comparer beaucoup de noms de symboles, et si ces noms peuvent atteindre plusieurs milliers de caractères, cela peut devenir assez coûteux).

    Et bien sûr, ils exacerbent les problèmes avec les fichiers d'en-tête, car les gabarits doivent généralement être définis dans les en-têtes, ce qui signifie que beaucoup plus de code doit être analysé et compilé pour chaque unité de compilation. En code C simple, un en-tête ne contient généralement que des déclarations directes, mais très peu de code réel. En C ++, il n'est pas rare que presque tout le code réside dans des fichiers d'en-tête.

  • Optimisation: C ++ permet des optimisations très spectaculaires. C # ou Java ne permettent pas d'éliminer complètement les classes (elles doivent être présentes à des fins de réflexion), mais même un simple métaprogramme de modèle C ++ peut facilement générer des dizaines ou des centaines de classes, toutes insérées et éliminées dans l'optimisation phase.

    De plus, un programme C ++ doit être entièrement optimisé par le compilateur. Un programme C # peut compter sur le compilateur JIT pour effectuer des optimisations supplémentaires au moment du chargement, C ++ n'obtient pas de telles "secondes chances". Ce que le compilateur génère est aussi optimisé qu'il le sera.

  • Langage machine: C ++ est compilé en code machine qui peut être un peu plus compliqué que le bytecode Java ou .NET utilisé (en particulier dans le cas de x86).
    (Ceci n'est mentionné que parce qu'il a été mentionné dans les commentaires et dans la pratique, mais il est peu probable que cette étape prenne plus qu'une infime fraction du temps total de compilation.)

La plupart de ces facteurs sont partagés par le code C, qui se compile assez efficacement. L'étape d'analyse syntaxique est beaucoup plus compliquée en C ++ et peut prendre beaucoup plus de temps, mais le délinquant principal est probablement des modèles. Ils sont utiles et font du langage C ++ un langage beaucoup plus puissant, mais ils prennent aussi leur péage en termes de vitesse de compilation.


722
2017-11-25 18:38



Le ralentissement n'est pas nécessairement le même avec un compilateur.

Je n'ai pas utilisé Delphi ou Kylix mais à l'époque de MS-DOS, un programme Turbo Pascal compilait presque instantanément, alors que le programme Turbo C ++ équivalent ne faisait que l'explorer.

Les deux principales différences étaient un système de module très fort et une syntaxe permettant la compilation en un seul passage.

Il est certainement possible que la vitesse de compilation n'ait pas été une priorité pour les développeurs de compilateurs C ++, mais il y a aussi des complications inhérentes à la syntaxe C / C ++ qui rendent le traitement plus difficile. (Je ne suis pas un expert en C, mais Walter Bright l'est, et après avoir construit divers compilateurs commerciaux en C / C ++, il a créé le langage D. Un de ses changements consistait à appliquer une grammaire sans contexte pour rendre le langage plus facile à analyser.)

De plus, vous remarquerez que Makefiles est généralement configuré de sorte que chaque fichier est compilé séparément en C, donc si 10 fichiers sources utilisent tous le même fichier include, ce fichier include est traité 10 fois.


36
2017-11-25 18:55



L'analyse syntaxique et la génération de code sont en fait plutôt rapides. Le véritable problème est l'ouverture et la fermeture des fichiers. Rappelez-vous, même avec les gardes d'inclusion, le compilateur a toujours ouvrir le fichier .H, et lire chaque ligne (et ensuite l'ignorer).

Un ami une fois (tout en s'ennuyant au travail), a pris l'application de son entreprise et a mis tout - tous les fichiers source et en-tête - dans un gros fichier. Le temps de compilation est passé de 3 heures à 7 minutes.


32
2017-11-25 19:01



C ++ est compilé en code machine. Vous avez donc le pré-processeur, le compilateur, l'optimiseur et enfin l'assembleur, qui doivent tous fonctionner.

Java et C # sont compilés en byte-code / IL, et la machine virtuelle Java / .NET Framework s'exécute (ou compile JIT en code machine) avant l'exécution.

Python est un langage interprété qui est également compilé en byte-code.

Je suis sûr qu'il y a d'autres raisons à cela, mais en général, ne pas avoir à compiler vers le langage machine natif fait gagner du temps.


16
2017-11-25 18:28



Une autre raison est l'utilisation du pré-processeur C pour localiser les déclarations. Même avec les gardes d'en-tête, .h doit toujours être analysé encore et encore, chaque fois qu'ils sont inclus. Certains compilateurs supportent des en-têtes pré-compilés qui peuvent aider, mais ils ne sont pas toujours utilisés.

Voir également: C ++ Réponses fréquemment posées


15
2017-11-25 18:32



Les plus gros problèmes sont:

1) L'en-tête infini reparsing. Déjà mentionné Les atténuations (comme #pragma une fois) ne fonctionnent généralement que par unité de compilation, pas par build.

2) Le fait que la chaîne d'outils est souvent séparée en plusieurs binaires (make, préprocesseur, compilateur, assembleur, archiver, impdef, linker et dlltool dans les cas extrêmes) qui doivent tous réinitialiser et recharger tout état tout le temps pour chaque invocation ( compilateur, assembleur) ou tous les deux fichiers (archiver, linker et dlltool).

Voir aussi cette discussion sur comp.compilers: http://compilers.iecc.com/comparch/article/03-11-078 spécialement celui-ci:

http://compilers.iecc.com/comparch/article/02-07-128

Notez que John, le modérateur de comp.compilers semble être d'accord, et que cela signifie qu'il devrait être possible d'atteindre des vitesses similaires pour C aussi, si l'on intègre complètement la chaîne d'outils et implémente des en-têtes précompilés. De nombreux compilateurs commerciaux C le font dans une certaine mesure.

Notez que le modèle Unix de tout factoriser en un binaire séparé est une sorte de modèle du pire des cas pour Windows (avec sa lente création de processus). Il est très visible lors de la comparaison des temps de construction de GCC entre Windows et * nix, en particulier si le système make / configure appelle également certains programmes uniquement pour obtenir des informations.


11
2018-05-02 11:30



Bâtiment C / C ++: que se passe-t-il vraiment et pourquoi cela prend-il autant de temps?

Une partie relativement importante du temps de développement logiciel n'est pas consacrée à l'écriture, à l'exécution, au débogage ou même à la conception de code, mais à l'attente de la fin de la compilation. Afin d'accélérer les choses, nous devons d'abord comprendre ce qui se passe lorsque les logiciels C / C ++ sont compilés. Les étapes sont à peu près les suivantes:

  • Configuration
  • Construire un outil de démarrage
  • Vérification de dépendance
  • Compilation
  • Mise en relation

Nous allons maintenant examiner chaque étape plus en détail en nous concentrant sur la façon dont elles peuvent être réalisées plus rapidement.

Configuration

C'est la première étape lorsque vous commencez à construire. Généralement, cela signifie exécuter un script de configuration ou CMake, Gyp, SCons ou un autre outil. Cela peut prendre entre une seconde et plusieurs minutes pour les très grands scripts de configuration basés sur Autotools.

Cette étape arrive relativement rarement. Il suffit de l'exécuter pour modifier les configurations ou modifier la configuration de la construction. À moins de changer de système de construction, il n'y a pas grand-chose à faire pour accélérer le processus.

Construire un outil de démarrage

C'est ce qui arrive quand vous lancez make ou cliquez sur l'icône de construction sur un IDE (qui est généralement un alias pour make). L'outil de construction binaire démarre et lit ses fichiers de configuration ainsi que la configuration de construction, qui sont généralement les mêmes.

Selon la complexité et la taille de la construction, cela peut durer de quelques secondes à plusieurs secondes. En soi, ce ne serait pas si mal. Malheureusement, la plupart des systèmes de génération basés sur des marques font invoquer des dizaines à des centaines de fois pour chaque build. Habituellement, cela est dû à l'utilisation récursive de make (ce qui est mauvais).

Il convient de noter que la raison pour laquelle Make est si lent n'est pas un bogue d'implémentation. La syntaxe de Makefiles a quelques bizarreries qui rendent une implémentation vraiment rapide, mais impossible. Ce problème est encore plus perceptible lorsqu'il est combiné avec l'étape suivante.

Vérification de dépendance

Une fois que l'outil de construction a lu sa configuration, il doit déterminer quels fichiers ont été modifiés et lesquels doivent être recompilés. Les fichiers de configuration contiennent un graphique acyclique dirigé décrivant les dépendances de construction. Ce graphique est généralement construit pendant l'étape de configuration. Le temps de démarrage de l'outil de construction et l'analyseur de dépendances sont exécutés sur chaque build. Leur exécution combinée détermine la limite inférieure du cycle edit-compile-debug. Pour les petits projets, cette période est généralement de quelques secondes. Ceci est tolérable. Il existe des alternatives à Make. Le plus rapide d'entre eux est Ninja, qui a été construit par les ingénieurs de Google pour Chromium. Si vous utilisez CMake ou Gyp pour construire, passez simplement à leurs backends Ninja. Vous n'êtes pas obligé de changer quoi que ce soit dans les fichiers de construction eux-mêmes, profitez simplement du gain de vitesse. Ninja n'est pas fourni sur la plupart des distributions, vous devrez donc peut-être l'installer vous-même.

Compilation

À ce stade, nous appelons finalement le compilateur. Couper quelques coins, voici les étapes approximatives prises.

  • La fusion inclut
  • Analyser le code
  • Génération de code / optimisation

Contrairement à la croyance populaire, compiler C ++ n'est pas vraiment lent. La liste STL est lente et la plupart des outils de compilation utilisés pour compiler C ++ sont lents. Cependant, il existe des outils plus rapides et des moyens d'atténuer les parties lentes du langage.

Les utiliser prend un peu de graisse de coude, mais les avantages sont indéniables. Des temps de construction plus rapides conduisent à des développeurs plus heureux, à plus d'agilité et, éventuellement, à un meilleur code.


9
2018-04-23 15:30



Un langage compilé nécessitera toujours un surcoût initial plus important qu'un langage interprété. En outre, vous n'avez peut-être pas très bien structuré votre code C ++. Par exemple:

#include "BigClass.h"

class SmallClass
{
   BigClass m_bigClass;
}

Compile beaucoup plus lentement que:

class BigClass;

class SmallClass
{
   BigClass* m_bigClass;
}

7
2017-11-25 18:33



Un moyen simple de réduire le temps de compilation dans les projets C ++ plus importants consiste à créer un fichier d'inclusion * .cpp incluant tous les fichiers cpp de votre projet et à le compiler. Cela réduit le problème d'explosion d'en-tête à une fois. L'avantage est que les erreurs de compilation référencent toujours le bon fichier.

Par exemple, supposons que vous ayez a.cpp, b.cpp et c.cpp .. créez un fichier: everything.cpp:

#include "a.cpp"
#include "b.cpp"
#include "c.cpp"

Ensuite, compilez le projet en faisant tout ce que vous voulez.


5
2018-03-03 22:35



Certaines raisons sont:

1) La grammaire C ++ est plus complexe que C # ou Java et prend plus de temps à analyser.

2) (Plus important) Le compilateur C ++ produit du code machine et effectue toutes les optimisations lors de la compilation. C # et Java vont juste à mi-chemin et laisser ces étapes à JIT.


4
2017-11-25 18:27



Le compromis que vous obtenez est que le programme fonctionne un peu plus vite. Cela peut vous rassurer en cours de développement, mais cela peut être très important une fois le développement terminé, et le programme est simplement géré par les utilisateurs.


4
2017-12-31 15:08