577 votes

Pourquoi la compilation du C++ est-elle si longue ?

La compilation d'un fichier C++ prend beaucoup de temps par rapport à 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. Comment cela se fait-il ?

Les deux raisons auxquelles j'ai pensé sont le chargement des fichiers d'en-tête et l'exécution du préprocesseur, mais cela ne semble pas pouvoir expliquer pourquoi cela prend autant de temps.

62 votes

VC++ supporte les en-têtes précompilés. Les utiliser vous aidera. Beaucoup.

1 votes

Oui, dans mon cas (principalement C avec quelques classes - pas de templates), les en-têtes précompilés accélèrent d'environ 10x.

0 votes

@Brian Je n'utiliserais jamais une tête pré-compilée dans une bibliothèque.

848voto

jalf Points 142628

Plusieurs raisons

Fichiers d'en-tête

Chaque unité de compilation nécessite des centaines, voire des milliers, d'en-têtes à (1) charger et (2) compiler. 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 le site raison principale, car il faut compiler d'énormes quantités de code pour chaque unité de compilation, et de plus, chaque en-tête doit être compilé plusieurs fois. (une fois pour chaque unité de compilation qui l'inclut).

Lien vers

Une fois compilés, tous les fichiers objets doivent être liés entre eux. Il s'agit d'un processus monolithique qui ne peut pas vraiment être parallélisé et qui doit traiter l'ensemble de votre projet.

Analyse syntaxique

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

Modèles

En C#, List<T> est le seul type qui est 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 distinct de vector<float> et chacune d'entre elles devra être compilée séparément.

Ajoutez à cela que les modèles constituent un "sous-langage" complet de Turing que le compilateur doit interpréter, et cela peut devenir ridiculement compliqué. Même un code de métaprogrammation de modèles 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 donner lieu à 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 un grand nombre 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 liés aux fichiers d'en-tête, car les modèles 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. Dans le code C ordinaire, un en-tête ne contient généralement que des déclarations prospectives, mais très peu de code réel. En C++, il n'est pas rare que la quasi-totalité du code réside dans les fichiers d'en-tête.

Optimisation

Le C++ permet des optimisations très importantes. Le 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èles C++ peut facilement générer des dizaines ou des centaines de classes, qui sont toutes intégrées et éliminées lors de la phase d'optimisation.

En outre, 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, Le C++ n'a pas de telles "secondes chances". Ce que le compilateur génère est aussi optimisé que possible.

Machine

Le C++ est compilé en code machine qui peut être un peu plus compliqué que le bytecode utilisé par Java ou .NET (surtout dans le cas de x86). (Ce point est mentionné par souci d'exhaustivité uniquement parce qu'il a été mentionné dans des commentaires et autres. En pratique, il est peu probable que cette étape prenne plus qu'une infime partie du temps total de compilation).

Conclusion

La plupart de ces facteurs sont partagés par le code C, qui se compile d'ailleurs assez efficacement. L'étape d'analyse syntaxique est beaucoup plus compliquée en C++, et peut prendre beaucoup plus de temps, mais le principal coupable est probablement les modèles. Ils sont utiles et font du C++ un langage beaucoup plus puissant, mais ils ont aussi un impact négatif sur la vitesse de compilation.

41 votes

Concernant le point 3 : la compilation en C est sensiblement plus rapide que celle en C++. C'est certainement le frontal qui cause le ralentissement, et non la génération du code.

0 votes

Je suis d'accord, comme je l'ai dit, c'est un facteur très faible. Je ne l'ai mentionné que parce que j'ai vu qu'il était mentionné dans certaines des autres réponses, et en le mentionnant ici par souci d'exhaustivité, je pouvais au moins souligner que ce n'était pas un gros problème. :)

78 votes

Concernant les templates : non seulement vector<int> doit être compilé séparément de vector<double>, mais vector<int> est recompilé dans chaque unité de compilation qui l'utilise. Les définitions redondantes sont éliminées par l'éditeur de liens.

48voto

James Curran Points 55356

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

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

17 votes

L'accès aux fichiers a certainement un rôle à jouer, mais comme l'a dit Jalf, la principale raison de ce problème est autre, à savoir l'analyse répétée de nombreux, nombreux, nombreux (imbriqués !) fichiers d'en-tête qui disparaissent complètement dans votre cas.

11 votes

C'est à ce moment-là que votre ami doit mettre en place des en-têtes précompilés, rompre les dépendances entre les différents fichiers d'en-tête (essayez d'éviter qu'un en-tête n'en inclue un autre, déclarez plutôt en avant) et se procurer un disque dur plus rapide. Cela mis à part, c'est une métrique assez étonnante.

6 votes

Si l'ensemble du fichier d'en-tête (à l'exception des éventuels commentaires et des lignes vides) se trouve dans les gardes d'en-tête, gcc est capable de se souvenir du fichier et de le sauter si le symbole correct est défini.

45voto

tangentstorm Points 3818

Le ralentissement n'est pas nécessairement le même avec tous les compilateurs.

Je n'ai pas utilisé Delphi ou Kylix, mais à l'époque de MS-DOS, un programme Turbo Pascal se compilait presque instantanément, tandis que le programme Turbo C++ équivalent se traînait.

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

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

Vous remarquerez également que les Makefiles sont généralement configurés de manière à ce que chaque fichier soit 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.

42 votes

La comparaison avec le Pascal est intéressante, car Niklaus Wirth utilisait le temps que mettait le compilateur à se compiler lui-même comme point de référence lors de la conception de ses langages et compilateurs. On raconte qu'après avoir soigneusement écrit un module pour la recherche rapide de symboles, il l'a remplacé par une simple recherche linéaire parce que la réduction de la taille du code rendait le compilateur plus rapide.

1 votes

@DietrichEpp L'empirisme est payant.

18voto

Alan Points 21367

Le C++ est compilé en code machine. Vous avez donc le préprocesseur, le compilateur, l'optimiseur et enfin l'assembleur, qui doivent tous être exécutés.

Java et C# sont compilés en byte-code/IL, et la machine virtuelle Java/le Framework .NET exécutent (ou compilent en 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, le fait de ne pas avoir à compiler en langage machine natif permet de gagner du temps.

17 votes

Le coût ajouté par le prétraitement est trivial. La principale "autre raison" du ralentissement est que la compilation est divisée en tâches distinctes (une par fichier objet), de sorte que les en-têtes communs sont traités encore et encore. C'est O(N^2) dans le pire des cas, contre O(N) pour la plupart des autres langages.

13 votes

On pourrait dire avec la même argumentation que les compilateurs C, Pascal etc. sont lents, ce qui n'est pas vrai en moyenne. Cela a plus à voir avec la grammaire du C++ et l'énorme état qu'un compilateur C++ doit maintenir.

3 votes

Le C est lent. Il souffre du même problème d'analyse d'en-tête que la solution acceptée. Par exemple, prenez un programme simple d'interface graphique Windows qui inclut Windows.h dans quelques unités de compilation, et mesurez les performances de compilation lorsque vous ajoutez des unités de compilation (courtes).

18voto

Dave Ray Points 20873

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

Voir aussi : Réponses aux questions fréquemment posées sur le C++

0 votes

Je pense que vous devriez mettre en gras le commentaire sur les en-têtes précompilés pour souligner cette partie IMPORTANTE de votre réponse.

6 votes

Si l'ensemble du fichier d'en-tête (à l'exception des éventuels commentaires et des lignes vides) se trouve dans les gardes d'en-tête, gcc est capable de se souvenir du fichier et de le sauter si le symbole correct est défini.

6 votes

@CesarB : Il doit toujours le traiter intégralement une fois par unité de compilation (fichier .cpp).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X