41 votes

Métriques quantifiables (benchmarks) sur l'utilisation des bibliothèques c ++ avec en-tête uniquement

J'ai essayé de trouver une réponse à cela en utilisant de la SORTE. Il y a un certain nombre de questions qui liste les avantages et les inconvénients de la construction d'un en-tête de la seule bibliothèque en c++, mais je n'ai pas été capable d'en trouver un qui le fait en termes quantifiables.

Donc, en termes quantifiables, ce qui est différent entre l'utilisation traditionnellement séparés en-tête c++ et la mise en œuvre des fichiers de rapport à en-tête uniquement?

Pour des raisons de simplicité, je suis en supposant que les modèles ne sont pas utilisés (car ils nécessitent d'en-tête uniquement).

D'élaborer, j'ai listé ce que j'ai vu dans les articles à être les avantages et les inconvénients. Évidemment, certains ne sont pas facilement quantifiables (tels que la facilité d'utilisation), et sont donc inutiles pour quantifiables comparaison. Je vais marquer ceux que j'attends quantifiables paramètres (quantifiables).

Avantages pour l'en-tête uniquement

  1. Il est plus facile à comprendre, puisque vous n'avez pas besoin de spécifier les options du linker dans votre système de construction.
  2. Vous avez toujours compiler tous le code de la bibliothèque avec le même compilateur (options) que le reste de votre code, puisque les fonctions de la bibliothèque obtenir insérée dans votre code.
  3. Il peut être beaucoup plus rapide. (quantifiables)
  4. Peut donner compilateur/linker de meilleures possibilités pour l'optimisation (explication/quantifiables, si possible)
  5. Est nécessaire si vous utilisez des modèles de toute façon.

Contre pour l'en-tête uniquement

  1. Il gonfle le code. (quantifiables) (comment cela affecte à la fois le temps d'exécution et l'empreinte mémoire)
  2. Plus les temps de compilation. (quantifiables)
  3. La perte de la séparation de l'interface et la mise en œuvre.
  4. Conduit parfois difficiles à résoudre les dépendances circulaires.
  5. Empêche la compatibilité binaire des bibliothèques partagées/Dll.
  6. Il peut aggraver des problèmes de co-travailleurs qui préfèrent les méthodes traditionnelles de l'aide de C++.

Tout les exemples que vous pouvez utiliser de plus grands, projets open source (comparaison de taille similaire, code) serait très apprécié. Ou, si vous connaissez un projet qui peut basculer entre l'en-tête uniquement et séparés versions (à l'aide d'un troisième fichier qui inclut à la fois), ce serait idéal. Anecdotique chiffres sont utiles car ils me donnent un stade avec qui je peux avoir une idée.

sources pour les avantages et inconvénients:

Merci à l'avance...

Mise à JOUR:

Pour tous ceux qui peuvent le lire plus tard et est intéressé à obtenir un peu d'information de fond sur la liaison et de la compilation, j'ai trouvé ces ressources utiles:

Mise à JOUR: (en réponse aux commentaires ci-dessous)

Tout simplement parce que les réponses peuvent varier, ne veut pas dire que la mesure est inutile. Vous avez pour commencer à mesurer comme certains point. Et le plus de mesures que vous avez, le plus clair de l'image est. Ce que je demande à cette question n'est pas toute l'histoire, mais un aperçu de l'image. Bien sûr, n'importe qui peut utiliser les nombres pour incliner un argument si ils voulaient manière contraire à l'éthique de promouvoir leurs préjugés. Cependant, si quelqu'un est curieux de connaître les différences entre les deux options et publie ces résultats, je pense que l'information est utile.

N'a pas été curieux à propos de ce sujet, assez pour le mesurer?

J'aime la fusillade de projet. Nous pourrions commencer par retirer la plupart de ces variables. Utilisez uniquement une version de gcc sur une version de linux. Seulement utiliser le même matériel pour tous les indices de référence. Ne pas compiler avec plusieurs threads.

Ensuite, on peut mesurer:

  • la taille de l'exécutable
  • runtime
  • empreinte mémoire
  • moment de la compilation (pour l'ensemble du projet et en modifiant un seul fichier)
  • lien temps

30voto

Realz Slaw Points 2303

Sommaire (points importants):

  • Deux forfaits comparés (une avec 78 unités de compilation, l'un avec 301 unités de compilation)
  • La traditionnelle Compilation (Multi Unité de Compilation) a abouti à une 7% plus rapide d'application (dans le 78 unité d'emballage); pas de changement dans l'exécution de l'application dans le 301 unité d'emballage.
  • À la fois Traditionnelle Compilation et d'en-Tête uniquement les indices de référence utilisés de la même quantité de mémoire lors de l'exécution (deux lots).
  • D'en-tête uniquement de la Compilation (une Seule Unité de Compilation) a abouti à une taille de l'exécutable qui était de 10% plus faible dans le 301 unité d'emballage (seulement 1% plus faible dans le 78 unité d'emballage).
  • La traditionnelle Compilation utilisé environ un tiers de la mémoire à la construction de plus de deux paquets.
  • La traditionnelle Compilation a pris trois fois plus de temps pour la compilation (sur la première compilation) et n'a pris que 4% du temps sur le recompiler (comme en-tête-n'a qu'à recompiler l'ensemble des sources).
  • La traditionnelle Compilation a pris plus de temps pour le lien sur le premier de la compilation et les compilations suivantes.

Box2D de référence, données:

box2d_data_gcc.csv

Botan de référence, données:

botan_data_gcc.csv

Box2D RÉSUMÉ (78 Unités)

enter image description here

Botan RÉSUMÉ (301 Unités)

enter image description here

NICE GRAPHIQUES:

Box2D la taille de l'exécutable:

Box2D executable size

Box2D compiler/lien/build/run temps:

Box2D compile/link/build/run time

Box2D compiler/lien/build/run max l'utilisation de la mémoire:

Box2D compile/link/build/run max memory usage

Botan la taille de l'exécutable:

Botan executable size

Botan compiler/lien/build/run temps:

Botan compile/link/build/run time

Botan compiler/lien/build/run max l'utilisation de la mémoire:

Botan compile/link/build/run max memory usage


Référence De Détails

TL;DR


Les projets testé, Box2D et Botan ont été choisis parce qu'ils sont potentiellement coûteuse en ressources informatiques, contiennent un bon nombre d'unités, et effectivement eu peu ou pas d'erreurs de compilation comme une seule unité. De nombreux autres projets ont été tentées, mais consommaient trop de temps pour "fixer" dans la compilation comme une seule unité. L'empreinte mémoire est mesurée par interrogation de la mémoire, à intervalles réguliers et en utilisant au maximum, et donc peut-être pas tout à fait exact.

Aussi, cette référence n'est pas automatique en-tête de la dépendance de la production (pour détecter d'en-tête change). Dans un projet en utilisant un autre système de construction, ce qui peut ajouter de temps pour tous les indices de référence.

Il y a 3 compilateurs dans l'indice de référence, chacun avec 5 configurations.

Compilateurs:

  • gcc
  • la cpi
  • clang

Compilateur configurations:

  • Par défaut par défaut des options du compilateur
  • Optimisé natif - -O3 -march=native
  • Taille optimisée - -Os
  • LTO/IPO natif - -O3 -flto -march=native avec clang et de gcc, -O3 -ipo -march=native à la cisp/cpi
  • Zéro optimisation - -Os

Je pense que ces chacun peut avoir différents paliers sur les comparaisons entre unitaires et le multi-versions. J'ai inclus LTO/IPO afin que nous puissions voir comment la "bonne" façon de parvenir à une seule unité-efficacité compare.

Explication des champs csv:

  • Test Name - nom de l'indice de référence. Exemples: Botan, Box2D.
  • Configuration de Test - nom d'une configuration particulière de ce test (spécial cxx drapeaux etc.). Généralement le même que Test Name.
  • Compiler - nom du compilateur utilisé. Exemples: gcc,icc,clang.
  • Compiler Configuration - nom de la configuration des options du compilateur utilisé. Exemple: gcc opt native
  • Compiler Version String - première ligne de la sortie du compilateur de la version du compilateur lui-même. Exemple: g++ --version produit g++ (GCC) 4.6.1 sur mon système.
  • Header only - une valeur de True si ce test a été construit comme une seule unité, False si elle a été construite comme un multi-projet de l'unité.
  • Units - nombre de parts dans le cas du test, même si il est construit comme une seule unité.
  • Compile Time,Link Time,Build Time,Run Time - qu'il y paraît.
  • Re-compile Time AVG,Re-compile Time MAX,Re-link Time AVG,Re-link Time MAX,Re-build Time AVG,Re-build Time MAX - à la fois à travers la reconstruction du projet, après avoir touché un seul fichier. Chaque unité est touchée, et, pour chacun, le projet est reconstruit. Les temps maximum, et la moyenne des temps sont enregistrés dans ces domaines.
  • Compile Memory,Link Memory,Build Memory,Run Memory,Executable Size - que le son.

Pour reproduire les repères:

  • Le bullwork est run.py.
  • Nécessite psutil (pour mémoire, les mesures).
  • Nécessite GNUMake.
  • Comme il est, il exige de la gcc, clang, icc/icpc dans le chemin d'accès. Peut être modifié pour supprimer l'un de ces bien sûr.
  • Chaque référence doit avoir un fichier de données qui répertorie les unités de repères. run.py ensuite créer deux cas de test, une avec chaque unité compilée séparément, et l'autre avec chaque unité réunies. Exemple: box2d.les données. Le format de fichier est défini comme une chaîne json, contenant un dictionnaire avec les touches suivantes
    • "units" - une liste d' c/cpp/cc fichiers qui composent les unités de ce projet
    • "executable" - Un nom de l'exécutable à être compilé.
    • "link_libs" - Une liste séparée par des espaces de bibliothèques installées lien.
    • "include_directores" - Une liste de répertoires à inclure dans le projet.
    • "command" - facultatif. commande à exécuter pour exécuter le test. Par exemple, "command": "botan_test --benchmark"
  • Pas tous les projets C++ cela peut être fait facilement avec; il doit y avoir aucun conflit/ambiguïtés dans la même unité.
  • Pour ajouter un projet à l'épreuve de cas, de modifier la liste test_base_cases dans run.py avec l'information pour le projet, y compris le nom de fichier de données.
  • Si tout se passe bien, le fichier de sortie data.csv doit contenir les résultats de référence.

Pour produire les graphiques à barres:

  • Vous devriez commencer avec un ensemble de données.csv fichier produit par l'indice de référence.
  • Obtenez de l' chart.py. Nécessite matplotlib.
  • Ajuster l' fields liste de décider quels sont les graphiques à produire.
  • Exécutez python chart.py data.csv.
  • Un fichier, test.png doit maintenant contenir le résultat.

Box2D

  • Box2D a été utilisé à partir de svn comme l'est, de la révision 251.
  • L'indice de référence a été prise à partir d' ici, modifiée ici et pourraient ne pas être représentatifs d'une bonne Box2D de référence, et il ne pourrait pas utiliser suffisamment de Box2D pour ce faire compilateur de référence de la justice.
  • Le box2d.fichier de données a été écrit manuellement, en trouvant tous .rpc unités.

Botan

  • À L'Aide De Botan-1.10.3.
  • Fichier de données: botan_bench.les données.
  • D'abord couru ./configure.py --disable-asm --with-openssl --enable-modules=asn1,benchmark,block,cms,engine,entropy,filters,hash,kdf,mac,bigint,ec_gfp,mp_generic,numbertheory,mutex,rng,ssl,stream,cvc, cela génère les fichiers d'en-tête et un Makefile.
  • J'ai désactivé l'assemblée, parce que l'assemblée peut intefere avec les optimisations qui peuvent se produire lorsque la fonction limites ne pas bloquer le processus d'optimisation. Cependant, c'est la conjecture et ne peut être totalement mauvais.
  • Puis a couru des commandes comme grep -o "\./src.*cpp" Makefile et grep -o "\./checks.*" Makefile pour obtenir le .rpc unités et de les mettre en botan_bench.les données de fichier.
  • Modifiés /checks/checks.cpp de ne pas appeler le x509 des tests unitaires, et a supprimé les x509 vérifier, en raison du conflit entre Botan typedef et openssl.
  • L'indice de référence inclus dans le Botan source a été utilisée.

Spécifications du système:

  • OpenSuse 11.4, 32 bits
  • 4 GO DE RAM
  • Intel(R) Core(TM) i7 CPU Q 720 @ 1.60GHz

28voto

Realz Slaw Points 2303

Mise à jour

C'était Vrai de Salade de chou, de l'original de la réplique. Sa réponse ci-dessus (accepté) est sa deuxième tentative. J'ai l'impression que sa deuxième tentative de réponses à la question entièrement. - Homer6

Eh bien, pour la comparaison, vous pouvez rechercher l'idée de "l'unité de la construction" (rien à voir avec le moteur graphique). En gros, une "unité de construire" est l'endroit où vous d'inclure tous les fichiers cpp dans un seul fichier, et de les compiler tous comme une seule unité de compilation. Je pense que cela devrait fournir une bonne comparaison, comme AFAICT, c'est l'équivalent de faire de votre projet d'en-tête uniquement. Vous seriez surpris de voir sur le 2ème "con" que vous avez énumérés; le point de l'ensemble de "l'unité s'appuie" sont de diminuer les temps de compilation. Soi-disant unité s'appuie compiler plus vite parce qu'ils:

.. sont un moyen de réduire le construire par-dessus-tête (plus précisément l'ouverture et la fermeture des fichiers et de réduire les temps de liaison en réduisant le nombre de fichiers objets générés) et en tant que tels sont utilisés pour accélérer les temps de construire.

altdevblogaday

Le temps de Compilation de comparaison (à partir d' ici):

enter image description here

Trois références principales pour "l'unité de construire:

Je suppose que vous voulez raisons pour les avantages et les inconvénients énumérés.

Avantages pour l'en-tête uniquement

[...]

3) Il peut être beaucoup plus rapide. (quantifiables) Le code peut être optimisé au mieux. La raison en est, lorsque les unités sont séparées, une fonction est un appel de fonction, et doit donc être de gauche si. Aucune information sur cet appel, c'est connu, par exemple:

  • Sera-ce la fonction de modification de la mémoire (et donc nos registres qui reflètent ceux des variables/mémoire sera obsolète quand il revient)?
  • Cette fonction mondiales de la mémoire (et donc on ne peut pas réorganiser où nous appelons la fonction)
  • etc.

En outre, si la fonction de code interne est connu, il pourrait être utile de l'inclure (c'est-à-vidage son code directement dans l'appel de la fonction). Inline évite l'appel de la fonction de surcharge. Inline permet également une foule d'autres optimisations pour se produire (par exemple, la constante de propagation; par exemple nous appelons factorial(10), maintenant, si le compilateur ne connaît pas le code de l' factorial(), il est forcé de le laisser comme cela, mais si nous savons que le code source de l' factorial(), on peut en fait les variables les variables de la fonction et de le remplacer avec de 10, et si nous sommes chanceux, nous pouvons même jusqu'à la fin avec la réponse au moment de la compilation, sans exécuter quoi que ce soit au moment de l'exécution). D'autres optimisations après l'in-lining comprennent dead-code d'élimination et (éventuellement) une meilleure direction de la prévision.

4) Peut donner compilateur/linker de meilleures possibilités pour l'optimisation (explication/quantifiables, si possible)

Je pense que cela découle de (3).

Contre pour l'en-tête uniquement

1) Il gonfle le code. (quantifiables) (comment cela affecte à la fois le temps d'exécution et l'empreinte mémoire) D'en-tête uniquement peut gonfler le code en quelques manières, que je sache.

Le premier est le modèle de la météorisation; où le compilateur instancie inutile de modèles de types qui ne sont jamais utilisés. Ce n'est pas particulier à en-tête uniquement, mais plutôt des modèles, et les compilateurs modernes ont amélioré pour le rendre peu préoccupants.

La deuxième méthode la plus utilisée, est la (sur)l'in-lining de fonctions. Si une grande fonction est incorporé partout où il est utilisé, ces appels de fonctions à grandir en taille. Ce qui aurait été une préoccupation au sujet de la taille de l'exécutable et exécutable-image-taille de la mémoire il y a des années, mais d'espace disque dur et la mémoire ont augmenté pour la rendre presque inutile de les préoccuper. La question la plus importante est que cette augmentation de la taille de la fonction peut ruiner le cache d'instructions (de sorte que la plus grande fonction ne rentre pas dans le cache, et maintenant, le cache doit être rempli que le PROCESSEUR exécute par le biais de la fonction). Enregistrer la pression sera augmenté après l'in-lining (il y a une limite sur le nombre de registres, la mémoire de l'unité centrale que le PROCESSEUR peut traiter directement). Cela signifie que le compilateur devra jongler avec les registres dans le milieu de la plus grande fonction, parce qu'il y a trop de variables.

2) de Plus les temps de compilation. (quantifiables)

Eh bien, en-tête uniquement compilation peut logiquement, plus les temps de compilation pour de nombreuses raisons (en dépit de la performance de "l'unité s'appuie"; la logique n'est pas nécessairement dans le monde réel, où d'autres facteurs impliqués). Une raison peut être, si un projet est d'en-tête uniquement, nous perdons des versions. Cela signifie un changement dans une partie du projet, l'ensemble du projet doit être reconstruit, alors qu'avec la compilation séparée des unités, des modifications d'une rpc signifie simplement que le fichier de l'objet doit être reconstruit, et le projet réédités.

Dans mon (anecdotique) de l'expérience, c'est un grand succès. En-tête uniquement) augmente les performances d'un lot dans certains cas particuliers, mais de la productivité sage, il n'est généralement pas la peine. Lorsque vous commencer à obtenir une plus grande base de code, le temps de compilation à partir de zéro peut prendre plus de 10 minutes à chaque fois. La recompilation sur un petit changement commence à devenir lassant. Vous ne savez pas combien de fois j'ai oublié un ";" et ont dû attendre 5 minutes pour entendre parler de lui, seulement pour revenir en arrière et corriger, et puis attendre encore 5 minutes pour trouver quelque chose d'autre, j'ai juste introduit en fixant le ";".

Performance est très bonne, la productivité est beaucoup mieux; il va perdre une grande partie de votre temps, et démotivent/vous distraire de votre programmation objectif.

Edit: je tiens à mentionner que interprocedural d'optimisation (voir aussi le lien-l'optimisation du temps, et de l' ensemble du programme d'optimisation) essaie d'accomplir l'optimisation des avantages de l ' "unité de construction". Les implémentations de cette est encore un peu fragile dans la plupart des compilateurs autant que je sache, mais finalement cela pourrait surmonter les avantages de performance.

4voto

Dukeling Points 31203

J'espère que ce n'est pas trop similaire à ce que Realz dit.

Exécutable (ou l'objet) taille: (exécutable 0% / objet jusqu'à 50% d'espace en plus sur l'en-tête uniquement)

Je suppose que les fonctions définies dans un fichier d'en-tête sera copié dans chaque objet. Quand il s'agit de la génération de l'exécutable, je dirais qu'il devrait être assez facile de couper les doublons de fonctions (aucune idée de ce qui les linkers faire/ne pas faire, je suppose que la plupart le font), donc (probablement) pas de réelle différence dans la taille de l'exécutable, mais bien dans la taille de l'objet. La différence doit en grande partie dépendre de la façon dont beaucoup de code est en fait dans les en-têtes par rapport au reste du projet. Non pas que la taille de l'objet qui compte vraiment, ces jours-ci, à l'exception de la liaison.

Runtime: (1%)

Je dirais que globalement identique (une adresse de fonction est une fonction de l'adresse), sauf pour les fonctions inline. Je m'attends à des fonctions inline pour faire moins de 1% de différence dans la moyenne de votre programme, car les appels de fonction ont une surcharge, mais ce n'est rien comparé à la surcharge de réellement faire quelque chose avec un programme.

Empreinte mémoire: (0%)

Même les choses dans l'exécutable = même empreinte mémoire (pendant l'exécution), en supposant que l'éditeur de liens coupes les doublons de fonctions. Si les doublons de fonctions ne sont pas coupés, il peut faire toute une différence.

Moment de la compilation (pour l'ensemble du projet et en modifiant un seul fichier): (ensemble jusqu'à 50% plus rapide que ce soit pour une, unique jusqu'à 99% plus rapide pour ne pas l'en-tête uniquement)

La différence est énorme. Changer quelque chose dans le fichier d'en-tête des causes de tout ce qui l'inclut à recompiler, tandis que les changements dans un fichier cpp exige juste que cet objet puisse être recréé, et un nouveau lien. Et facile 50% de plus pour une compilation complète pour l'en-tête uniquement les bibliothèques. Cependant, avec la pré-compilation des en-têtes ou de l'unité s'appuie, d'une compilation complète avec en-tête uniquement les bibliothèques serait probablement plus rapide, mais un changement nécessitant beaucoup de fichiers à recompiler est un énorme désavantage, et je dirais qu'en fait pas la peine. Plein recompile ne sont pas souvent utilisés. Aussi, vous pouvez inclure quelque chose dans un fichier cpp, mais pas dans son fichier d'en-tête (cela peut arriver souvent), de sorte que, dans un bon programme conçu (arbre comme la structure de dépendance / modularité), lors de la modification d'une déclaration de fonction ou de quelque chose (toujours requiert des changements dans le fichier d'en-tête), d'en-tête uniquement causerait beaucoup de choses à recompiler, mais pas d'en-tête uniquement, vous pouvez limiter fortement.

La liaison: (jusqu'à 50% plus rapide pour en-tête uniquement)

Les objets sont probablement plus grand, il serait donc prendre plus de temps pour les traiter. Probablement linéairement proportionnelle à façon beaucoup plus importante les dossiers sont. De mon expérience limitée dans de grands projets (où compiler + lien de temps assez longue pour en fait de l'importance), la liaison est presque négligeable par rapport au moment de la compilation (sauf si vous continuez à faire de petits changements et de la construction, alors j'imagine que vous vous sentiriez-il, qui, je suppose, peut arriver souvent).

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