web-dev-qa-db-fra.com

Répartir le grand projet C ++

Comme il arrive à beaucoup, notre projet C++ s'est développé plus grand et plus grand, et il a finalement frappé le point où la main-d'œuvre commence à être une préoccupation, principalement en raison des heures de construction: même en utilisant ccache, chaque changement nécessite de 30 Quelques secondes à 5 minutes pour reconstruire et tester (en réalité, il semble que "lier" est la phase de consommation de temps, mais nous enquêtons toujours).

Le projet consiste principalement en une grande bibliothèque scientifique, ainsi que des applications qui en font de l'usage. Par exemple, il existe des applications pour augmenter les modèles de simulation ou les liaisons de langue (Python) pour l'intégration dans d'autres packages logiciels.

Maintenant, il semblerait naturel de diviser la bibliothèque en bibliothèques plus petites: le code est déjà organisé dans des "modules", ce qui ne serait donc pas un problème. Le problème est de la gestion de la dépendance: nous pouvons penser à l'ensemble du projet en tant que big [~ # ~ # ~ ~] , où chaque nœud est un module de bibliothèque ou une bibliothèque externe (en raison de La nature du projet, de nombreuses dépendances externes sur d'autres bibliothèques scientifiques).

Donc, idéalement, lorsque vous travaillez sur un module, nous souhaitons reconstruire uniquement ce module, plus peut-être l'application de test associée et le lier avec ses dépendances. Cela accélérerait considérablement le cycle de développement.

Par conséquent, l'idée est de gérer chaque "module" en tant que projet indépendant, qui sera intégré séparément dans une bibliothèque (statique). J'ai deux préoccupations avec cette approche:

  • Imaginez que je travaille sur l'application A qui dépend de la bibliothèque B, qui dépend elle-même de la bibliothèque C. Au cours du développement, je constate que j'ai besoin d'une nouvelle fonctionnalité de la bibliothèque C, donc je vais donc de l'avant et du code. Je dois maintenant reconstruire manuellement la bibliothèque C, puis la bibliothèque B et enfin la bibliothèque A avant de continuer avec le développement. Lorsque la chaîne de dépendance augmente, cela peut être problématique.
  • Si les dépendances d'un module changent, comment gérer l'impact sur tout le DAG (d'autres modules qui changent en conséquence)?

Je suis en train de rechercher dans plusieurs outils susceptibles de contribuer à mon scénario. GIT Sumdules est une option, mais cela ne concerne pas mes préoccupations ci-dessus. Apache Ivy ne semble pas convient à ce que j'essaie d'atteindre. Je soupçonne que la réponse implique un système de construction puissant (nous utilisons QMake ), donc j'ai examiné cmake (mais j'ai trouvé son langage de script limité et difficile à apprendre), et ensuite Buck et pantalon .

Je ne suis toujours pas sûr si c'est la voie à suivre, alors je serais reconnaissant si quelqu'un pouvait partager des conseils et/ou des expériences.

6
matpen

Vous mentionnez des bibliothèques statiques dans votre question. Je suppose (peut-être à tort) que vous codez un programme pour Linux.

Vous devriez utiliser des bibliothèques partagées lorsque cela est possible. Ensuite, vous avez besoin de temps de liaison beaucoup moins (au détriment de la liaison d'exécution, qui est généralement assez rapide, à l'heure de début).

Pendant le développement, je trouve que j'ai besoin d'une nouvelle fonctionnalité dans la bibliothèque C, donc je vais de l'avant et codez cela. Je dois maintenant reconstruire manuellement la bibliothèque C, puis la bibliothèque B et enfin la bibliothèque A avant de continuer avec le développement.

Cela ne devrait pas être le cas des bibliothèques partagées (tant que leur API n'a pas changé).

Considérez peut-être une architecture à l'aide d'une architecture plugins et qui charges dynamiquement eux (par exemple avec dlopen sur POSIX).

il semble que "lier" est la phase de consommation de temps

Vous devez être sûr que c'est le cas (par ex. Par passant-time ou même -ftime-report à g++). Ensuite, vous pouvez utiliser le liant Gold et/ou visibility fonction attribut avec g++.

Assurez-vous que les API de vos sous-modules ou de vos bibliothèques ne changent pas souvent.

Évitez également Small Fichiers Source C++ , E.G. Préférez avoir dix fichiers source C++ de deux mille lignes chacun plutôt que cent fichiers source C++ de deux centaines de lignes chacune. Un fichier source C++ peut définir et mettre en œuvre plusieurs fonctions (associées) ou classes. N'oubliez pas que C++ n'a pas de modules et que la plupart des en-têtes standard (et vos propres) sont des fichiers assez gros (par exemple #include <vector> élargit à environ 10kloc sur mon bureau Linux); Ce qui compte pour le temps de compilation est la taille de la forme prétraitée et les expansions des modèles ont également besoin de beaucoup de temps. Voir aussi Ceci .

BTW, vous devez également Utilisez une bonne Build Automation outil (tel que Ninja , - gnu make - avec la parallélisation activée à travers make -j 8, etc...). Vous pouvez également utiliser des outils de construction distribués ( DISTCC , CECREAM , ...)

Vous pourriez également (avec [~ # ~] gcc [~ # ~ ~] ) envisagez d'utiliser un en-tête précompilé . Ensuite, vous voudrez probablement avoir un seul fichier d'en-tête, y compris tous les autres. Vous voudrez peut-être utiliser le PIMPL Idiom .

Voir aussi cette réponse à une question pertinente.


chaque changement nécessite n'importe où de 30 secondes à 5 minutes pour reconstruire et tester

BTW, un temps de construction incrémentielle de cinq minutes semble plutôt petit pour moi. Pourquoi tu te plains? Je suis assez vieux pour avoir eu dans les années 1990, sur de puissants postes de travail Sun de cette époque, construire des heures de presque une heure (pour un logiciel de quelques centaines de milliers de lignes que j'ai écrites pendant plusieurs années). Et je contribue parfois à la GCC elle-même même ces années, dont le temps de reconstruction prend plusieurs heures.

7

Il existe déjà de bonnes réponses liées aux systèmes de construction et aux libs partagés, mais je vais aborder cela sous un autre angle que je ne vois pas les personnes qui s'appliquent aussi souvent.

Pour moi, il est utile de séparer un code stable (comme il est peu probable que cela ait jamais besoin de nouvelles modifications) d'un code instable (code qui garantit naturellement des modifications supplémentaires à mesure que le logiciel se développe). Si vous ne pouvez pas le faire et voir l'intégralité de votre codeBase comme pièces potentiellement mobiles, même si les interfaces sont complètement stables, je pense que vous faites quelque chose de mal.

Nous avons déjà tendance à le faire naturellement pour les bibliothèques tiers open source. Nous les construisons généralement une fois, créez une dylib idéalement pour éviter les heures de liaison statiques et vient de rechercher des symboles et des fonctions d'appel entre eux au moment de l'exécution. Nous ne trouvons pas besoin de les reconstruire constamment depuis et que nous n'avons aucun intérêt à toucher leur code source, en utilisant uniquement les fonctionnalités disponibles - construisez-la une fois et utilisez pour toujours. Le code est 100% stable à cet égard (il n'ya aucune raison de changer, du moins pas dans nos mains).

Donc, pour des tiers, je les ai personnellement placés dans un sous-répertoire "tiers_parties" et je ne construis que lorsque j'ajoute une nouvelle bibliothèque tiers ou remplacez une ancienne version avec une nouvelle version extrêmement rare, comme une fois tous les quelques mois minimum. Je ne reconstruis pas ceux de plus et encore quotidiennement.

La même chose devrait être capable de s'appliquer à votre propre codeBase. J'ai une chose similaire où j'ai un sous-répertoire de "Libs" avec mon propre code et j'ai rarement construit les bibliothèques résultantes, car le code est très stable, fiable, efficace, testé soigneusement testé avec des tests unitaires, voire une analyse statique, et non quelque chose que je besoin de changer dans le futur. Il est séparé loin du code instable à l'extérieur du sous-répertoire "Libs" qui doit changer quotidiennement qui se rétablissent à plusieurs reprises.

Donc, séparant votre codeBase de cette façon, où des pièces stables que vous devez rarement, si jamais, de passer des pièces instables qui justifient constamment des modifications peuvent être un moyen utile d'organiser votre codeBase - non seulement en termes d'optimisation des temps de construction, mais afin de mieux séparer Les forfaits stables de packages instables avec un objectif pour se retrouver autant de code stables que possible que vous pouvez dire avec confiance que vous ne garantirez aucun changement dans un avenir proche.

Lorsque vous faites cela, ces parties stables de votre basebase peuvent être construites séparément et loin des pièces instables en les utilisant. Les pièces instables ne doivent pas prendre si longtemps à construire comme un ensemble stable de bibliothèques étendues et étendues tandis que les pièces instables rétrécissent et rétrécissent.

Cependant, cela a tendance à impliquer certaines duplication de code. Pour pouvoir créer une bibliothèque d'images stable, il ne peut pas dépendre de, par exemple, une bibliothèque de mathématiques instable ou une autre modification de la bibliothèque de mathématiques justifiera une reconstruction si elle ne modifie plus d'informations sur la bibliothèque d'images. Donc, je le trouve utile parfois, par exemple, une bibliothèque d'images pour dupliquer certaines routines mathématiques afin de devenir indépendantes de toute bibliothèque de mathématiques auxiliaire. Dans ce cas, le découplage du code de cette manière à travers une duplication modeste de la logique peut réellement aider à rendre ces paquets beaucoup plus stables, en éliminant ainsi les raisons de devoir changer et/ou être reconstruits de plus. Si le code est bien testé et fonctionne de manière magnifique pendant des années à venir et qui aurait peut-être pu être reconstruit à peine, la double duplication requise pour que la bibliothèque atteigne son indépendance et sa stabilité ne pose guère de problème.

En outre, il aide à atteindre le minimalisme dans vos interfaces. Si votre bibliothèque d'images a pour objectif de mettre en œuvre chaque opération d'image jamais imaginable à l'humanité, ses ambitions et sa nature monolithique justifieront de ne jamais modifier. Il ne peut jamais espérer devenir une bibliothèque stable avec de tels objectifs. S'il vise uniquement à fournir des opérations d'image de base ou même aucune avec la possibilité de créer des opérations d'image en dehors de la bibliothèque à l'aide de ce que la bibliothèque fournit, il peut potentiellement atteindre un état de stabilité parfaite (ne consistant aucune raison de changer de futur).

Donc, de toute façon, si vous allez commencer à fractionnez une base de code, je suggérer de commencer à fractionnez les pièces stables et bien testées où vous êtes à peu près sûr que vous n'avez pas besoin de les modifier d'autres pièces instables où Vous anticipez au moins une possibilité de besoin de changements futurs. Construire les pièces stables doit invoquer un processus de construction distinct (rarement appliqué) des pièces instables (fréquemment reconstruite).

3
user204677

Je ne sais pas quel est votre environnement/budget mais voici quelques suggestions:

  • Visual Studio 2017 surtout avec /MP, en-têtes précompilés, constructions incrémentielles, et éventuellement C++ modules
  • incrédibuild (jamais utilisé personnellement)
  • octobuild (jamais utilisé personnellement)
  • zapcc peut être beaucoup plus rapide que les compilateurs grand public

Les conseils habituels s'appliquent aux projets simples et multiples: Exécutez le meilleur CPU + RAM + SSD (ou même Ramdisk) que vous pouvez acheter, désactiver la numérisation de virus à accès sur l'accès et modifier rarement les en-têtes dans la mesure du possible.

1
Graeme Wicksted

Juste au cas où il ne serait pas évident: utilisez des en-têtes précompilés et dites à FCC de courir plus d'une instance du compilateur.

Pour la liaison, la mise en place de code dans une bibliothèque A partagé devrait aider. Certains systèmes de construction utilisent des optimisations de temps de liaison qui pourraient ajouter aux heures de construction et vous pourrez peut-être l'éteindre.

Et quel genre d'ordinateur avez-vous? Si vous souffrez de temps de construction, 8 cœurs et beaucoup de RAM peut être une solution facile. Et s'il est possible de passer de GCC en clang, cela pourrait vous aider.

0
gnasher729

Dans un projet assez important que j'ai travaillé, nous avions toutes connues de bonnes versions de Libs d'autres projets publiés pour la consommation, sauf si vous n'aviez que le projet choisi, auquel cas cela le compilait.

Cependant, vous devrez peut-être réviser vos contextes bornés ou votre flux de travail pour réaliser réellement les problèmes. Définissez les limites du module afin de réduire la nécessité de travailler sur C qui chatouille à B qui chatouille à un ou les travaux de la manière logique afin que le travail de préparation soit conforme au canal de libération de C avant de travailler sur B et un démarrage.

0
Martin K