Comment faire face au problème de (compiler) une grande base de code?

10

Bien que je puisse coder, je n'ai pas encore d'expérience dans le travail sur de grands projets. Ce que j'ai fait jusqu'à présent était soit de coder de petits programmes qui se compilaient en quelques secondes (divers exercices c / c ++ comme des algorithmes, des principes de programmation, des idées, des paradigmes, ou simplement d'essayer des API ...) ou de travailler sur des projets plus petits qui étaient fait dans un ou des langages de script (python, php, js) où aucune compilation n'est nécessaire.

Le truc, c'est que lorsque je code dans un langage de script, chaque fois que je veux essayer si quelque chose fonctionne - je lance simplement le script et vois ce qui se passe. Si les choses ne fonctionnent pas, je peux simplement changer le code et l'essayer à nouveau en exécutant le script à nouveau et continuer à le faire jusqu'à ce que j'obtienne le résultat que je voulais. Mon point est que vous n'avez pas à attendre rien à compiler et à cause de cela, il est assez facile de prendre une grande base de code, de la modifier, d'y ajouter quelque chose ou de simplement jouer avec - vous pouvez voir les changements instantanément.

Comme exemple, je prendrai Wordpress. Il est assez facile d'essayer de comprendre comment créer un plugin pour cela. D'abord, vous commencez par créer un simple plugin "Hello World", puis vous créez une interface simple pour le panneau d'administration pour vous familiariser avec l'API, puis vous le construisez et faites quelque chose de plus complexe, en attendant de changer l'apparence de quelques fois .. L'idée d'avoir à recompiler quelque chose d'aussi gros que WP maintes et maintes fois, après chaque modification mineure pour essayer "si cela fonctionne" et "comment cela fonctionne / se sent" semble juste inefficace, lente et erronée.

Maintenant, comment pourrais-je faire cela avec un projet écrit dans un langage compilé? Je voudrais contribuer à certains projets open-source et cette question continue de me déranger. La situation diffère probablement d'un projet à l'autre où certains d'entre eux qui ont été pensés au préalable avec sagesse seront d'une certaine manière "modulaires" tandis que d'autres ne seront qu'une grosse goutte qui doit être recompilée encore et encore.

J'aimerais en savoir plus sur la façon dont cela se fait correctement. Quelles sont les pratiques, approches et conceptions de projets (modèles?) Communes pour y faire face? Comment appelle-t-on cette "modularité" dans le monde des programmeurs et à quoi dois-je google pour en savoir plus? Est-ce souvent que les projets sortent de leurs proportions de première pensée qui deviennent gênantes après un certain temps? Existe-t-il un moyen d'éviter une longue compilation de projets pas si bien conçus? Un moyen de les modulariser d'une manière ou d'une autre (peut-être en excluant des parties non vitales du programme pendant le développement (d'autres idées?))?

Merci.

pootzko
la source
4
Ob. XKCD et le t-shirt thinkgeek pertinent * 8 ')
Mark Booth
1
Si vous travaillez sur un projet assez grand avec un budget assez important, vous pouvez obtenir des serveurs de construction pour faire la compilation pour vous :)
SoylentGray
@Chad - Je le sais, mais c'est juste ma machine de bureau gnu / linux à la maison et moi en ce moment :)
pootzko
@Chad Ok, alors vous nous dites que nous avons besoin de serveurs dédiés pour gérer le gros de Java (ou tout autre langage compilé)? C'est de la merde totale
Kolob Canyon
1
@KolobCanyon - Non, je dis qu'il y a une échelle à laquelle vous pourriez travailler qui les obligerait. et qu'ils sont suffisamment bon marché maintenant qu'il est assez facile d'avoir une machine virtuelle à la demande dédiée à la compilation et à l'autmation rapides des tests pour que l'échelle ne soit pas si grande.
SoylentGray

Réponses:

8

Tout comme cela a été dit, vous ne recompilez jamais l'intégralité du projet chaque fois que vous effectuez une petite modification. Au lieu de cela, vous recompilez uniquement la partie du code qui a changé, ainsi que tout le code qui en dépend.

En C / C ++, la compilation est assez simple. Vous compilez la traduction de chaque fichier source en code machine (nous les appelons des fichiers objets * .o), puis vous liez tous vos fichiers objets en un seul grand exécutable.

Tout comme MainMa l'a mentionné, certaines bibliothèques sont intégrées dans des fichiers séparés, qui seront liés dynamiquement au moment de l'exécution avec l'exécutable. Ces bibliothèques sont appelées objets partagés (* .so) dans Unix et bibliothèques liées dynamiquement (DLL) dans Windows. Les bibliothèques dynamiques présentent de nombreux avantages, dont le fait que vous n'avez pas besoin de les compiler / lier, à moins que leur code source ne change effectivement.

Il existe des outils d'automatisation de construction qui vous aident à:

  • Spécifiez les dépendances entre les différentes parties de votre arborescence source.
  • Lancer des compilations ponctuelles et discrètes uniquement dans la partie qui a été modifiée.

Les plus célèbres (make, ant, maven, ...) peuvent détecter automatiquement quelles parties du code ont été modifiées depuis la dernière compilation, et exactement quel objet / binaire doit être mis à jour.

Cependant, cela entraîne un coût (relativement faible) d'avoir à écrire un "script de construction". C'est un fichier contenant toutes les informations sur votre build, comme définir les cibles et leurs dépendances, définir quel compilateur vous voulez et quelles options utiliser, définir votre environnement de build, vos chemins de bibliothèque, ... Vous avez peut-être entendu parler de Makefiles (très commun dans le monde Unix), ou build.xml (très populaire dans le monde Java). Voilà ce qu'ils font.

rahmu
la source
2
Ant (Java) n'est pas en mesure de déterminer ce qui doit être recompilé. Il gère la partie triviale du travail, recompile le code source modifié, mais ne comprend pas du tout les dépendances de classe. Nous nous appuyons sur les IDE pour cela, et ils tournent mal si une signature de méthode est modifiée d'une manière qui ne nécessite pas de changement de code d'appel.
kevin cline
@kevincline J'appuie ceci - ANT compile tout sauf si vous spécifiez quelque chose de différent dans le build.xmlfichier
Kolob Canyon
7

Vous ne recompilez pas le projet entier à chaque fois. Par exemple, s'il s'agit d'une application C / C ++, il y a des chances qu'elle soit séparée en bibliothèques (DLL sous Windows), chaque bibliothèque étant compilée séparément.

Le projet lui-même est généralement compilé quotidiennement sur un serveur dédié: ce sont des builds nocturnes. Ce processus peut prendre beaucoup de temps, car il inclut non seulement le temps de compilation, mais aussi le temps passé à exécuter des tests unitaires, d'autres tests et d'autres processus.

Arseni Mourzenko
la source
3
Si je ne recompile pas tout, quand
aurai-
5

Je pense que toutes les réponses à ce jour ont également fait allusion, c'est que les grands projets logiciels sont presque toujours divisés en morceaux beaucoup plus petits. Chaque pièce est normalement stockée dans son propre fichier.

Ces pièces sont compilées individuellement pour créer des objets. Les objets sont ensuite liés entre eux pour former le produit final. [D'une certaine manière, c'est un peu comme construire des trucs à partir de Legos. Vous n'essayez pas de mouler la chose finale à partir d'un gros morceau de plastique, mais vous combinez un tas de petits morceaux pour le faire.]

Diviser le projet en morceaux qui sont compilés individuellement permet à certaines choses intéressantes de se produire.

Bâtiment incrémental

Tout d'abord, lorsque vous changez une pièce, vous n'avez généralement pas besoin de recompiler toutes les pièces. D'une manière générale, tant que vous ne modifiez pas la façon dont les autres pièces interagissent avec votre pièce, les autres n'ont pas besoin d'être recompilées.

Cela donne naissance à l'idée de construction incrémentale . Lorsque vous effectuez une génération incrémentielle, seules les parties affectées par la modification sont recompilées. Cela accélère considérablement le temps de développement. Certes, vous devrez peut-être encore attendre que tout soit rétabli, mais cela représente toujours une économie par rapport à la nécessité de recompiler et de tout relier à nouveau. (BTW: Certains systèmes / langages prennent en charge la liaison incrémentielle de sorte que seules les choses qui ont changé doivent être reliées. Le coût pour cela est généralement lié aux performances et à la taille du code.)

Tests unitaires

La deuxième chose que d'avoir de petits morceaux vous permet de faire est de tester individuellement les morceaux avant de les combiner. C'est ce qu'on appelle les tests unitaires . Dans les tests unitaires, chaque unité est testée individuellement avant d'être intégrée (combinée) avec le reste du système. Les tests unitaires sont normalement écrits afin de pouvoir être exécutés rapidement sans impliquer le reste du système.

Le cas limite d'application des tests est vu dans Test Driven Development (TDD). Dans ce modèle de développement, aucun code n'est écrit / modifié sauf pour corriger un test qui a échoué.

Pour le rendre plus facile

Donc, décomposer les choses semble bien, mais il semble également que beaucoup de travail soit nécessaire pour construire le projet: vous devez comprendre ce que les morceaux ont changé et ce qui dépend de ces morceaux, compiler chaque morceau, puis lier le tout ensemble.

Heureusement, les programmeurs sont paresseux *, ils inventent donc beaucoup d'outils pour faciliter leur travail. À cette fin, de nombreux outils ont été écrits pour automatiser la tâche ci-dessus. Les plus célèbres d'entre eux ont déjà été mentionnés (marque, fourmi, maven). Ces outils vous permettent de définir quelles pièces doivent être assemblées pour faire votre projet final et comment les pièces dépendent les unes des autres (c'est-à-dire si vous changez cela, cela doit être recompilé). Le résultat est que l'émission d'une seule commande détermine ce qui doit être recompilé, le compile et relie tout.

Mais cela laisse encore à comprendre comment les choses sont liées les unes aux autres. C'est beaucoup de travail et comme je l'ai déjà dit, les programmeurs sont paresseux. Ils ont donc trouvé une autre classe d'outils. Ces outils ont été écrits pour déterminer les dépendances pour vous! Souvent, les outils font partie des environnements de développement intégrés (IDE) comme Eclipse et Visual Studio, mais il existe également des outils autonomes utilisés pour les applications génériques et spécifiques (makedep, QMake pour les programmes Qt).

* En fait, les programmeurs ne sont pas vraiment paresseux, ils aiment juste passer leur temps à travailler sur des problèmes, pas à faire des tâches répétitives qui peuvent être automatisées par un programme.

jwernerny
la source
5

Voici ma liste de choses que vous pouvez essayer d'accélérer les builds C / C ++:

  • Êtes-vous configuré pour reconstruire uniquement ce qui a changé? La plupart des environnements le font par défaut. Il n'est pas nécessaire de recompiler un fichier s'il ou aucun des en-têtes n'a changé. De même, il n'y a aucune raison de reconstruire une dll / exe si tous les liens dans objs / lib n'ont pas changé.
  • Mettez des éléments tiers qui ne changent jamais et les en-têtes associés dans une zone de bibliothèque de code en lecture seule. Vous n'avez besoin que des en-têtes et des fichiers binaires associés. Vous ne devriez jamais avoir besoin de reconstruire cela à partir d'une source autre qu'une seule fois.
  • Lors de la reconstruction de tout, les deux facteurs limitants de mon expérience ont été le nombre de cœurs et la vitesse du disque . Obtenez un quad core robuste, une machine hyperthreadée avec un très bon disque dur et vos performances s'amélioreront. Considérez un disque SSD - gardez à l'esprit que les disques bon marché peuvent être pires qu'un bon disque dur. Pensez à utiliser raid pour augmenter votre disque dur
  • Utilisez un système de build distribué tel qu'Incredibuild qui répartira la compilation entre les autres postes de travail de votre réseau. (Assurez-vous que vous disposez d'un réseau solide).
  • Configurez une génération d'unité pour vous éviter de recharger constamment les fichiers d'en-tête.
Doug T.
la source
D'après mon expérience (pas beaucoup, mais bien), la vitesse du disque commence à devenir hors de propos si votre projet dépasse "très petit". Pensez à ce que vous dites dans votre prochaine puce: vous utilisez le réseau pour accélérer la compilation. Si le disque était un gros goulot d'étranglement, le recours au réseau ne semble pas une très bonne décision.
R. Martinho Fernandes
Une autre solution bon marché consiste à compiler dans un tmpfs. Peut augmenter considérablement les performances si le processus de compilation est lié aux E / S.
Artefact2
4

L'idée de devoir recompiler quelque chose d'aussi gros que WP encore et encore, après chaque modification mineure pour essayer «si cela fonctionne» et «comment cela fonctionne / se sent» semble juste inefficace, lente et erronée.

L'exécution de quelque chose interprété est également très inefficace et lente, et (sans doute) erronée. Vous vous plaignez des exigences de temps sur le PC du développeur, mais ne pas compiler entraîne des exigences de temps sur le PC de l' utilisateur , ce qui est sans doute bien pire.

Plus important encore, les systèmes modernes peuvent effectuer des reconstructions incrémentielles assez avancées et il n'est pas courant de recompiler le tout pour des modifications mineures - les systèmes compilés peuvent inclure des composants de script, particulièrement communs pour des choses comme l'interface utilisateur.

DeadMG
la source
1
Je crois que ma question n'était pas censée être interprétée par rapport au débat sur l'approche de compilation. Au lieu de cela, je viens de demander des conseils sur la façon de développer correctement un grand projet (compilé). Merci pour l'idée de reconstructions incrémentielles.
pootzko
@pootzko: Eh bien, il est assez injuste de discuter des inconvénients de la compilation lorsque vous ne parlez pas également des inconvénients de l'interprétation.
DeadMG
1
non ce n'est pas. c'est un autre débat et n'a rien à voir avec ma question. Je ne dis pas que c'est quelque chose qui ne devrait pas être discuté. ça devrait, mais pas ici.
pootzko
@pootzko: Dans ce cas, vous ne devriez pas consacrer la majorité de votre question à énumérer ce que vous n'aimez pas lors de la compilation. Vous devriez avoir écrit quelque chose de beaucoup plus court et plus succinct, comme "Comment réduire les temps de compilation de grands projets?".
DeadMG
Je ne savais pas que je devais demander à quelqu'un comment je «devrais» poser ma question ..? : OI l'a écrit comme je l'ai fait pour mieux expliquer mon point de vue afin que d'autres puissent mieux le comprendre et m'expliquer comment réaliser la même chose / similaire avec les langages compilés. Encore une fois, je n'ai pas demandé à quiconque de me dire si les langages interprétés entraînaient une aggravation des délais sur le PC de l'utilisateur. Je le sais, et cela n'a rien à voir avec ma question - "comment est-ce fait avec les langues compilées", désolé. D'autres personnes semblent avoir compris ce que j'ai demandé, donc je ne pense pas que ma question ne soit pas assez claire ..
pootzko
4
  • Reconstruction partielle

Si le projet implémente le DAG de dépendance de compilation appropriée, vous pouvez vous en sortir en recompilant uniquement les fichiers objets affectés par votre modification.

  • Processus de compilation multiple

En supposant également un DAG de dépendance de compilation approprié, vous pouvez compiler à l'aide de plusieurs processus. Un travail par cœur / unité centrale est la norme.

  • Tests exécutables

Vous pouvez créer plusieurs exécutables pour les tests qui ne lient que des fichiers objets particuliers.

Dietbuddha
la source
2

En plus de la réponse de MainMa, nous venons également de mettre à niveau les machines sur lesquelles nous travaillons. L'un des meilleurs achats que nous ayons faits était un SSD lorsque vous ne pouvez pas vous empêcher de recompiler l'intégralité du projet.

Une autre suggestion serait d'essayer un compilateur différent. À l'époque, nous passons du compilateur Java à Jikes et nous sommes maintenant passés à l'utilisation du compilateur fourni avec Eclipse (je ne sais pas s'il a un nom) qui tire mieux parti des processeurs multicœurs.

Notre projet de 37 000 fichiers a pris environ 15 minutes à compiler à partir de zéro avant d'apporter ces modifications. Après les changements, il a été réduit à 2-3 minutes.

Bien sûr, il vaut la peine de mentionner à nouveau le point de MainMa. Ne recompilez pas l'intégralité du projet chaque fois que vous souhaitez voir un changement.

RP.
la source