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

La compilation d’un fichier C ++ prend beaucoup de temps par rapport à C # et à Java. Il faut beaucoup plus de temps pour comstackr 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 pour lesquelles je pouvais penser étaient de charger des fichiers d’en-tête et d’exécuter le préprocesseur, mais cela ne semble pas devoir expliquer pourquoi cela prend trop de temps.

Plusieurs raisons:

  • Fichiers d’en-tête: chaque unité de compilation nécessite des centaines, voire des milliers d’en-têtes pour être 1: chargée et 2: compilée. 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 peut varier 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 énormément de code à comstackr pour chaque unité de compilation, et chaque en-tête doit être compilé plusieurs fois (une fois pour chaque unité de compilation qui le comprend).

  • Liaison: Une fois compilés, tous les fichiers objects doivent être liés ensemble. C’est fondamentalement un processus monolithique qui ne peut pas très bien être parallélisé et qui doit traiter l’intégralité de votre projet.

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

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

    Ajoutez à cela que les gabarits constituent un “sous-langage” complet que le compilateur doit interpréter, et cela 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 dizaines et des dizaines d’instanciations de modèles. Les modèles peuvent également générer des types extrêmement complexes, avec des noms ridiculement longs, ce qui ajoute beaucoup de travail supplémentaire à l’éditeur de liens. (Il doit comparer beaucoup de noms de symboles, et si ces noms peuvent devenir plusieurs milliers de caractères, cela peut devenir assez coûteux).

    Et bien sûr, ils aggravent les problèmes avec les fichiers d’en-tête, car les modèles doivent généralement être définis dans des 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 là à des fins de reflection), mais même un simple métaprogramme de modèle C ++ peut facilement générer des dizaines ou des centaines de classes. phase.

    De plus, un programme C ++ doit être entièrement optimisé par le compilateur. Le programme AC # peut compter sur le compilateur JIT pour effectuer des optimisations supplémentaires au moment du chargement, le C ++ n’obtient aucune “seconde chance”. Ce que le compilateur génère est aussi optimisé que possible.

  • Code machine: C ++ est compilé en code machine, ce qui peut être un peu plus compliqué que l’utilisation du bytecode Java ou .NET (en particulier dans le cas de x86).
    (Ceci est mentionné par souci d’exhaustivité uniquement parce qu’il a été mentionné dans les commentaires et autres. En pratique, il est peu probable que cette étape prenne plus d’une petite fraction du temps total de compilation.)

La plupart de ces facteurs sont partagés par le code C, qui comstack en réalité assez efficacement. L’étape d’parsing est beaucoup plus compliquée en C ++ et peut prendre beaucoup plus de temps, mais l’auteur principal est probablement les modèles. Ils sont utiles et font du C ++ un langage beaucoup plus puissant, mais ils ont également un impact sur la vitesse de compilation.

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

Je n’ai pas utilisé Delphi ou Kylix, mais de retour dans les jours MS-DOS, un programme Turbo Pascal se compilait presque instantanément, alors que le programme Turbo C ++ équivalent ne faisait qu’explorer.

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

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

En outre, vous remarquerez que les Makefiles sont généralement configurés de manière à ce que chaque fichier soit compilé séparément dans C, donc si 10 fichiers sources utilisent tous le même fichier include, celui-ci est traité 10 fois.

L’parsing 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 ouvert le fichier .H et lit chaque ligne (puis l’ignore).

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

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 octet-code / IL et la machine virtuelle Java / .NET Framework s’exécute (ou comstack JIT en code machine) avant son exécution.

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

Je suis sûr qu’il y a d’autres raisons à cela, mais en général, ne pas avoir à comstackr en langage machine natif permet de gagner du temps.

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

Voir aussi: Réponses fréquentes à C ++

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 soit souvent séparée en plusieurs binarys (make, preprocessor, comstackr, assembler, archiver, impdef, linker et dlltool dans des cas extrêmes) qui doivent tous réinitialiser et recharger tous les états pour chaque appel ( compilateur, assembleur) ou tous les deux fichiers (archiveur, éditeur de liens et dlltool).

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

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

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

Notez que le modèle Unix consistant à tout intégrer à un binary distinct est une sorte de pire modèle pour Windows (avec sa création de processus lente). Il est très notable lorsque l’on compare les temps de construction GCC entre Windows et * nix, surtout si le système make / configure appelle également certains programmes uniquement pour obtenir des informations.

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 du logiciel n’est pas consacrée à l’écriture, à l’exécution, au débogage ou même à la conception du code, mais à 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 approximativement les suivantes:

  • Configuration
  • Construire le démarrage de l’outil
  • Vérification des dépendances
  • 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 se produit relativement rarement. Il suffit de l’exécuter pour modifier les configurations ou modifier la configuration de la construction. À moins de changer les systèmes de construction, il n’ya pas grand chose à faire pour accélérer cette étape.

Construire le démarrage de l’outil

C’est ce qui se passe lorsque vous lancez make ou cliquez sur l’icône de construction d’un IDE (qui est généralement un alias pour make). L’outil de construction binary démarre et lit ses fichiers de configuration ainsi que la configuration de la 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 grave. 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 impossible. Ce problème est encore plus perceptible lorsqu’il est combiné avec l’étape suivante.

Vérification des dépendances

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 lors de l’étape de configuration. Le temps de démarrage de l’outil de génération et le scanner de dépendance sont exécutés sur chaque génération. Leur exécution combinée détermine la limite inférieure du cycle edit-comstack-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’avez rien à changer dans les fichiers de construction eux-mêmes, profitez simplement de la vitesse. Ninja n’est cependant pas packagé sur la plupart des dissortingbutions, vous devrez donc l’installer vous-même.

Compilation

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

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

Contrairement à la croyance populaire, la compilation de C ++ n’est pas si lente. La STL est lente et la plupart des outils de compilation utilisés pour comstackr 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 au 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.

Un langage compilé va toujours exiger une surcharge initiale plus importante qu’un langage interprété. De plus, vous n’avez peut-être pas très bien structuré votre code C ++. Par exemple:

 #include "BigClass.h" class SmallClass { BigClass m_bigClass; } 

Comstack beaucoup plus lentement que:

 class BigClass; class SmallClass { BigClass* m_bigClass; } 

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 comstackr. Cela réduit le problème d’explosion d’en-tête à une fois. L’avantage de ceci est que les erreurs de compilation référenceront toujours le fichier correct.

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, comstackz le projet en faisant tout ce que vous voulez.

Quelques raisons sont:

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

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

Le compromis que vous obtenez est que le programme fonctionne un peu plus vite. Cela peut être un réconfort pour vous pendant le développement, mais cela peut avoir une grande importance une fois le développement terminé et le programme est simplement exécuté par les utilisateurs.

La plupart des réponses ne sont pas très claires en mentionnant que C # fonctionnera toujours plus lentement en raison du coût des actions qui ne sont exécutées qu’une seule fois en C ++. Ce coût de performance est également affecté par les dépendances à l’exécution (plus de choses à charger). à exécuter), sans compter que les programmes C # auront toujours une empreinte mémoire plus élevée, ce qui se traduira par des performances plus étroitement liées à la capacité du matériel disponible. La même chose est vraie pour les autres langages interprétés ou dépendant d’une VM.

Je pense que deux problèmes peuvent affecter la vitesse de compilation de vos programmes en C ++.

PROBLEME POSSIBLE N ° 1 – COMPILATION DE L’EN-TETE: (Cela peut ou non avoir déjà été traité par une autre réponse ou un autre commentaire). Microsoft Visual C ++ (AKA VC ++) prend en charge les en-têtes précompilés, que je recommande fortement. Lorsque vous créez un nouveau projet et sélectionnez le type de programme que vous effectuez, une fenêtre de l’assistant d’installation doit apparaître sur votre écran. Si vous cliquez sur le bouton “Suivant>” au bas de la fenêtre, la fenêtre vous mènera à une page contenant plusieurs listes de fonctionnalités; Assurez-vous que la case à côté de l’option “En-tête précompilé” est cochée. (NOTE: Cela a été mon expérience avec les applications de console Win32 en C ++, mais cela peut ne pas être le cas avec tous les types de programmes en C ++.)

POSSIBLE NUMÉRO 2 – L’EMPLACEMENT À COMPILER: Cet été, j’ai suivi un cours de programmation et nous avons dû stocker tous nos projets sur des clés USB de 8 Go, car les ordinateurs du laboratoire que nous utilisions ont été effacés tous les soirs à minuit. ce qui aurait effacé tout notre travail. Si vous comstackz sur un périphérique de stockage externe pour des raisons de portabilité / sécurité / etc, cela peut prendre beaucoup de temps (même avec les en-têtes précompilés que j’ai mentionnés ci-dessus) pour que votre programme se comstack, surtout s’il est assez volumineux. programme. Mon conseil pour vous dans ce cas serait de créer et de comstackr des programmes sur le disque dur de l’ordinateur que vous utilisez, et à chaque fois que vous voulez / devez arrêter de travailler sur vos projets pour quelque raison que ce soit, transférez-les sur votre ordinateur externe. périphérique de stockage, puis cliquez sur l’icône «Retirer le périphérique en toute sécurité et éjecter le média», qui doit apparaître sous forme de petit lecteur flash derrière un petit cercle vert avec une coche blanche pour la déconnecter.

J’espère que ceci vous aide; faites-moi savoir si c’est le cas! 🙂

Comme déjà indiqué, le compilateur passe beaucoup de temps à instancier et à instancier à nouveau les modèles. À tel point que certains projets se concentrent sur cet élément particulier et revendiquent une accélération observable de 30 fois dans certains cas vraiment favorables. Voir http://www.zapcc.com .