web-dev-qa-db-fra.com

Pourquoi la compilation C ++ prend-elle si longtemps?

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

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

509
Dan Goldstein

Plusieurs raisons

Fichiers d'en-tête

Chaque unité de compilation nécessite que des centaines, voire des milliers d'en-têtes soient (1) chargés et (2) compilés. Chacun d'entre eux doit généralement être recompilé pour chaque unité de compilation, car le pré-processeur garantit que le résultat de la compilation d'un en-tête peut-être varie d'une unité de compilation à l'autre. (Une macro peut être définie dans une unité de compilation qui modifie le contenu de l'en-tête).

Ceci est probablement la principale raison, car cela nécessite d'énormes quantités de code à compiler pour chaque unité de compilation et, en outre, 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 d’objets doivent être liés. Il s'agit fondamentalement d'un processus monolithique qui ne peut pas très bien être mis en parallèle et doit traiter l'ensemble de votre projet.

L'analyse

La syntaxe est extrêmement compliquée à analyser, dépend énormément du contexte et est très difficile à distinguer des ambiguïtés. Cela prend beaucoup de temps.

Modèles

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

Ajoutez à cela que les modèles constituent un "sous-langage" complet de Turing que le compilateur doit interpréter, ce qui peut devenir ridiculement compliqué. Même un 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 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 faut comparer beaucoup de noms de symboles, et si ces noms peuvent contenir plusieurs milliers de caractères, cela peut devenir assez coûteux).

Et bien sûr, ils aggravent 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 qu'il faut analyser et compiler davantage de code pour chaque unité de compilation. En code C simple, un en-tête ne contient généralement que des déclarations en aval, mais très peu de code. En C++, il n’est pas rare que la quasi-totalité du code réside dans des fichiers d’en-tête.

Optimisation

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

De plus, un programme C++ doit être entièrement optimisé par le compilateur. Un programme C # peut s’appuyer sur le compilateur JIT pour effectuer des optimisations supplémentaires au moment du chargement, le C++ n’obtenant aucune de ces "secondes chances". Ce que le compilateur génère est aussi optimisé que possible.

Machine

C++ est compilé en code machine, ce qui peut être un peu plus compliqué que le bytecode Java ou .Net Use (en particulier dans le cas de x86). (Ceci est mentionné de manière complète uniquement parce que cela a été mentionné dans les commentaires, etc.). En pratique, il est peu probable que cette étape prenne plus d'une fraction du temps total de compilation.

Conclusion

La plupart de ces facteurs sont partagés par le code C, qui compile de manière assez efficace. L'étape de l'analyse syntaxique est beaucoup plus complexe en C++ et peut prendre beaucoup plus de temps, mais le principal responsable est probablement les modèles. Ils sont utiles et font du C++ un langage beaucoup plus puissant, mais ils nuisent également à la rapidité de la compilation.

769
jalf

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

Je n'ai pas utilisé Delphi ni Kylix, mais à l'époque MS-DOS, un programme Turbo Pascal était compilé presque instantanément, alors que le programme Turbo C++ équivalent se contentait d'analyser.

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

Il est certainement possible que la vitesse de compilation ne soit tout simplement pas une priorité pour les développeurs de compilateurs C++, mais il existe également 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. n de ses changements consistait à appliquer une grammaire sans contexte. pour rendre la langue plus facile à analyser.)

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 en C; ainsi, si 10 fichiers sources utilisent tous le même fichier include, celui-ci est traité 10 fois.

37
tangentstorm

L'analyse et la génération de code sont en fait assez rapides. Le vrai problème est d'ouvrir et de fermer des fichiers. N'oubliez pas que même avec les protecteurs d'inclusion, le compilateur a toujours ouvert le fichier .H et a lu chaque ligne (puis l'ignore).

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

36
James Curran

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

Java et C # sont compilés dans le code octet/IL, et la Java machine virtuelle/.NET Framework est exécutée (ou compilée JIT dans le code machine) avant l’exécution.

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

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

15
Alan

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

Voir aussi: Réponses fréquemment posées dans C++

15
Dave Ray

Les plus gros problèmes sont:

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

2) Le fait que la chaîne d’outils est souvent séparée en plusieurs fichiers binaires (marque, pré-processeur, compilateur, assembleur, archiveur, impdef, éditeur de liens et dlltool dans les cas extrêmes) qui obligent tous à réinitialiser et à recharger tout l’état à tout moment pour chaque invocation ( compilateur, assembleur) ou tous les deux fichiers (archiveur, éditeur de liens et dlltool).

Voir aussi cette discussion sur comp.compilers: http://compilers.iecc.com/comparch/article/03-11-078 spécialement celle-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 également, si l’on intégrait pleinement la chaîne d’outils et implémentait les en-têtes précompilés. De nombreux compilateurs C commerciaux le font dans une certaine mesure.

Notez que le modèle Unix de tout factoriser dans un binaire séparé est une sorte de pire modèle pour Windows (avec sa création de processus lente). Il est très important de comparer les temps de génération GCC entre Windows et * nix, en particulier si le système make/configure appelle également certains programmes uniquement pour obtenir des informations.

12

Building C/C++: qu'est-ce qui se passe réellement et pourquoi cela prend-il si longtemps

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 le logiciel C/C++ est compilé. Les étapes sont approximativement les suivantes:

  • Configuration
  • Mise en service de l'outil
  • Vérification de dépendance
  • Compilation
  • Mise en relation

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

Configuration

C'est la première étape lorsque vous commencez à construire. Cela signifie généralement que vous devez exécuter un script de configuration ou CMake, Gyp, SCons ou un autre outil. Cela peut prendre de quelques secondes à plusieurs minutes pour de très grands scripts de configuration basés sur Autotools.

Cette étape se produit relativement rarement. Il ne doit être exécuté que lors de la modification des configurations ou de la configuration de la construction. Sans changer de système de construction, il n'y a pas grand chose à faire pour accélérer cette étape.

démarrage de l'outil de construction

C'est ce qui se produit lorsque vous exécutez make ou cliquez sur l'icône de construction sur un IDE (qui est généralement un alias de make). Le binaire de l'outil de génération démarre et lit ses fichiers de configuration, ainsi que la configuration de la construction, qui sont généralement identiques.

En fonction de la complexité et de la taille de la construction, cette opération peut durer d'une fraction de seconde à plusieurs secondes. En soi, cela ne serait pas si grave. Malheureusement, la plupart des systèmes de compilation basés sur les marques invoquent des dizaines à des centaines de reprises pour chaque compilation. Généralement, 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 très rapide presque impossible. Ce problème est encore plus perceptible lorsqu'il est associé à l'étape suivante.

vérification des dépendances

Une fois que l'outil de génération a lu sa configuration, il doit déterminer quels fichiers ont été modifiés et quels fichiers doivent être recompilés. Les fichiers de configuration contiennent un graphe 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 édition-compilation-débogage. Pour les petits projets, ce temps est généralement de quelques secondes. C'est tolérable. Il existe des alternatives à faire. 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, il suffit de passer à leurs backends Ninja. Vous n'avez rien à changer dans les fichiers de construction eux-mêmes, profitez simplement de l'amélioration de la vitesse. Ninja n’est cependant pas inclus dans la plupart des distributions, vous devrez donc l’installer vous-même.

Compilation

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

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

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

Leur utilisation 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.

11
Ravindra Acharya

Un langage compilé nécessitera toujours 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;
}

Compile beaucoup plus lentement que:

class BigClass;

class SmallClass
{
   BigClass* m_bigClass;
}
7
Andy Brice

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 de ceci est que les erreurs de compilation feront toujours référence au bon fichier.

Par exemple, supposons que vous avez 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 simplement everything.cpp

6
rileyberton

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 laissent ces étapes à JIT.

4
Nemanja Trifunovic

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

4
T.E.D.

La plupart des réponses manquent un peu de clarté en mentionnant que C # fonctionnera toujours plus lentement en raison du coût d'exécution des actions effectuées en C++ une seule fois lors de la compilation. Ce coût en termes de performances est également affecté par les dépendances d'exécution à exécuter), sans oublier que les programmes C # auront toujours une empreinte mémoire plus importante, ce qui se traduit par une relation plus étroite entre les performances et la capacité du matériel disponible. Il en va de même pour les autres langues interprétées ou dépendantes d'une machine virtuelle.

2
Panic

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

NUMÉRO POSSIBLE N ° 1 - COMPILER L'EN-TÊTE: (Cela peut ou peut ne pas avoir déjà été abordé par une autre réponse ou un autre commentaire.) Microsoft Visual C++ (A.K.A. VC++) prend en charge les en-têtes précompilés, ce que je recommande vivement. Lorsque vous créez un nouveau projet et sélectionnez le type de programme que vous créez, une fenêtre de l’assistant d’installation doit apparaître à l’écran. Si vous cliquez sur le bouton "Suivant>" en bas de celui-ci, la fenêtre vous mènera à une page contenant plusieurs listes de fonctionnalités. assurez-vous que la case en regard de l'option "En-tête précompilé" est cochée. (REMARQUE: C’est ce que j’ai connu avec les applications de console Win32 en C++, mais ce n’est peut-être pas le cas de tous les types de programmes en C++.)

NUMÉRO POSSIBLE N ° 2 - L'EMPLACEMENT À COMPILER AVEC: 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, comme les ordinateurs du laboratoire que nous utilisions. été effacé tous les soirs à minuit, ce qui aurait effacé tout notre travail. Si vous compilez sur un périphérique de stockage externe pour des raisons de portabilité/sécurité/etc., cela peut prendre un temps très long (même avec le fichier précompilé en-têtes que j'ai mentionnés ci-dessus) à compiler par votre programme, surtout s'il s'agit d'un programme assez volumineux. Dans ce cas, mon conseil est de créer et de compiler des programmes sur le disque dur de l’ordinateur que vous utilisez et, chaque fois que vous souhaitez/devez arrêter de travailler sur votre/vos projet (s) pour quelque raison que ce soit, transférez-le sur votre compte 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 devrait apparaître sous la forme d’un petit lecteur flash derrière un petit cercle vert avec une coche blanche, pour le déconnecter.

J'espère que ceci vous aide; laissez-moi savoir si c'est le cas! :)

1
cjor530

Comme déjà commenté, le compilateur passe beaucoup de temps à instancier et à instancier à nouveau les modèles. À tel point qu'il existe des projets qui se concentrent sur cet élément particulier et réclament une accélération observable 30 fois plus rapide dans certains cas vraiment favorables. Voir http://www.zapcc.com .

0
akim