Accélérer la compilation 2,5X grâce au découplage des dépendances et à la conteneurisation des tests
Le temps de compilation peut être aggravé par des dépendances internes et externes complexes qui évoluent tout au long du processus de développement, ainsi que par des changements dans les environnements de compilation tels que le système d’exploitation ou les architectures matérielles. Voici des problèmes courants que l’on peut rencontrer en travaillant sur des projets d’IA ou de MLOps à grande échelle :
Compilation excessivement longue - L’intégration du code est effectuée des centaines de fois par jour. Avec des centaines de milliers de lignes de code en place, même une petite modification peut entraîner une compilation complète qui prend généralement une ou plusieurs heures.
Environnement de compilation complexe - Le code du projet doit être compilé dans différents environnements, qui impliquent différents systèmes d’exploitation, tels que CentOS et Ubuntu, des dépendances sous-jacentes, telles que GCC, LLVM et CUDA, ainsi que des architectures matérielles. Et une compilation dans un environnement spécifique peut normalement ne pas fonctionner dans un autre environnement.
Dépendances complexes - La compilation du projet implique plus de 30 dépendances entre composants et de tiers. Le développement du projet entraîne souvent des changements dans les dépendances, provoquant inévitablement des conflits de dépendances. Le contrôle des versions entre les dépendances est si complexe que la mise à jour de la version des dépendances affectera facilement d’autres composants.
Le téléchargement des dépendances tierces est lent ou échoue - Les retards réseau ou les bibliothèques de dépendances tierces instables entraînent des téléchargements de ressources lents ou des échecs d’accès, affectant sérieusement l’intégration du code.
En découplant les dépendances et en mettant en œuvre la conteneurisation des tests, nous avons réussi à réduire le temps de compilation moyen de 60 % en travaillant sur le projet open-source de recherche de similarité d’embeddings Milvus.
Découpler les dépendances du projet
La compilation d’un projet implique généralement un grand nombre de dépendances de composants internes et externes. Plus un projet a de dépendances, plus leur gestion devient complexe. À mesure que le logiciel se développe, il devient plus difficile et coûteux de modifier ou de supprimer des dépendances, ainsi que d’identifier les effets de ces actions. Une maintenance régulière est nécessaire tout au long du processus de développement pour garantir que les dépendances fonctionnent correctement. Une mauvaise maintenance, des dépendances complexes ou des dépendances défectueuses peuvent provoquer des conflits qui ralentissent ou bloquent le développement. En pratique, cela peut signifier des téléchargements de ressources qui prennent du retard, des échecs d’accès qui ont un impact négatif sur l’intégration du code, et plus encore. Découpler les dépendances du projet peut atténuer les défauts et réduire le temps de compilation, accélérant les tests système et évitant une charge inutile sur le développement logiciel.
Par conséquent, nous recommandons de découpler les dépendances de votre projet :
- Diviser les composants avec des dépendances complexes
- Utiliser différents référentiels pour la gestion des versions.
- Utiliser des fichiers de configuration pour gérer les informations de version, les options de compilation, les dépendances, etc.
- Ajouter les fichiers de configuration aux bibliothèques de composants afin qu’ils soient mis à jour au fil des itérations du projet.
Optimisation de la compilation entre les composants — Extraire et compiler le composant pertinent en fonction des dépendances et des options de compilation enregistrées dans les fichiers de configuration. Étiqueter et empaqueter les résultats de compilation binaires et les fichiers manifest correspondants, puis les téléverser vers votre référentiel privé. Si aucune modification n’est apportée à un composant ou aux composants dont il dépend, rejouer ses résultats de compilation en fonction des fichiers manifest. Pour les problèmes tels que les retards réseau ou les bibliothèques de dépendances tierces instables, essayez de mettre en place un référentiel interne ou d’utiliser des référentiels miroirs.
Pour optimiser la compilation entre les composants :
1.Créer un graphe de relations de dépendance — Utiliser les fichiers de configuration dans les bibliothèques de composants pour créer un graphe de relations de dépendance. Utiliser la relation de dépendance pour récupérer les informations de version (Git Branch, Tag et Git commit ID), les options de compilation et davantage d’informations sur les composants dépendants en amont et en aval.
Figure 1.
2.Vérifier les dépendances — Générer des alertes pour les dépendances circulaires, les conflits de versions et les autres problèmes qui surviennent entre les composants.
3.Aplatir les dépendances — Trier les dépendances par parcours en profondeur (DFS) et fusionner en amont les composants ayant des dépendances dupliquées afin de former un graphe de dépendances.
Figure 2.
4.Utiliser l’algorithme MerkleTree pour générer un hachage (Root Hash) contenant les dépendances de chaque composant sur la base des informations de version, des options de compilation, et plus encore. Combiné à des informations telles que le nom du composant, l’algorithme forme une balise unique pour chaque composant.
Figure 3.
5.Sur la base des informations de balise unique du composant, vérifier si une archive de compilation correspondante existe dans le dépôt privé. Si une archive de compilation est récupérée, la décompresser pour obtenir le fichier manifeste pour la relecture ; sinon, compiler le composant, baliser les fichiers objets de compilation générés et le fichier manifeste, puis les téléverser vers le dépôt privé.
Mettre en œuvre des optimisations de compilation au sein des composants — Choisir un outil de cache de compilation propre au langage pour mettre en cache les fichiers objets compilés, puis les téléverser et les stocker dans votre dépôt privé. Pour la compilation C/C++, choisir un outil de cache de compilation comme CCache pour mettre en cache les fichiers intermédiaires de compilation C/C++, puis archiver le cache CCache local après la compilation. Ces outils de cache de compilation mettent simplement en cache les fichiers de code modifiés un par un après la compilation, et copient les composants compilés du fichier de code inchangé afin qu’ils puissent être directement impliqués dans la compilation finale. L’optimisation de la compilation au sein des composants comprend les étapes suivantes :
- Ajouter les dépendances de compilation nécessaires au Dockerfile. Utiliser Hadolint pour effectuer des contrôles de conformité sur le Dockerfile afin de s’assurer que l’image respecte les bonnes pratiques de Docker.
- Créer un miroir de l’environnement de compilation selon la version de sprint du projet (version + build), le système d’exploitation et d’autres informations.
- Exécuter le conteneur de l’environnement de compilation mis en miroir, et transmettre l’ID de l’image au conteneur en tant que variable d’environnement. Voici un exemple de commande pour obtenir l’ID de l’image : “docker inspect ‘ — type=image’ — format ‘{{.ID}}’ repository/build-env:v0.1-centos7”.
- Choisir l’outil de cache de compilation approprié : Entrer dans votre conteneur pour intégrer et compiler vos codes et vérifier dans votre dépôt privé si un cache de compilation approprié existe. Si oui, le télécharger et l’extraire dans le répertoire spécifié. Une fois tous les composants compilés, le cache généré par l’outil de cache de compilation est empaqueté et téléversé vers votre dépôt privé en fonction de la version du projet et de l’ID de l’image.
Optimisation supplémentaire de la compilation
Notre build initial occupe trop d’espace disque et de bande passante réseau, et prend beaucoup de temps à déployer, nous avons pris les mesures suivantes :
- Choisir l’image de base la plus légère pour réduire la taille de l’image, par ex. alpine, busybox, etc.
- Réduire le nombre de couches d’image. Réutiliser les dépendances autant que possible. Fusionner plusieurs commandes avec “&&”.
- Nettoyer les produits intermédiaires pendant la construction de l’image.
- Utiliser le cache d’image pour construire l’image autant que possible.
À mesure que notre projet continue de progresser, l’utilisation du disque et des ressources réseau a commencé à monter en flèche avec l’augmentation du cache de compilation, tandis que certains caches de compilation sont sous-utilisés. Nous avons ensuite effectué les ajustements suivants :
Nettoyer régulièrement les fichiers de cache — Vérifier régulièrement le dépôt privé (à l’aide de scripts, par exemple), et nettoyer les fichiers de cache qui n’ont pas changé depuis un certain temps ou qui n’ont pas été beaucoup téléchargés.
Mise en cache sélective de la compilation — Ne mettre en cache que les compilations gourmandes en ressources, et ignorer la mise en cache des compilations qui ne nécessitent pas beaucoup de ressources.
Exploiter les tests conteneurisés pour réduire les erreurs, améliorer la stabilité et la fiabilité
Les codes doivent être compilés dans différents environnements, qui impliquent divers systèmes d’exploitation (p. ex. CentOS et Ubuntu), des dépendances sous-jacentes (p. ex. GCC, LLVM et CUDA), et des architectures matérielles spécifiques. Un code qui se compile avec succès dans un environnement spécifique échoue dans un autre environnement. En exécutant les tests à l’intérieur de conteneurs, le processus de test devient plus rapide et plus précis.
La conteneurisation garantit que l’environnement de test est cohérent et qu’une application fonctionne comme prévu. L’approche de test conteneurisée empaquette les tests sous forme de conteneurs d’images et construit un environnement de test véritablement isolé. Nos testeurs ont constaté que cette approche était assez utile, ce qui a fini par réduire les temps de compilation jusqu’à 60 %.
Garantir un environnement de compilation cohérent — Comme les produits compilés sont sensibles aux changements de l’environnement système, des erreurs inconnues peuvent se produire dans différents systèmes d’exploitation. Nous devons étiqueter et archiver le cache du produit compilé en fonction des changements dans l’environnement de compilation, mais ils sont difficiles à catégoriser. Nous avons donc introduit la technologie de conteneurisation pour unifier l’environnement de compilation afin de résoudre ces problèmes.
Conclusion
En analysant les dépendances du projet, cet article présente différentes méthodes d’optimisation de la compilation entre les composants et au sein de ceux-ci, fournissant des idées et de bonnes pratiques pour construire une intégration continue du code stable et efficace. Ces méthodes ont contribué à résoudre la lenteur de l’intégration du code causée par des dépendances complexes, à unifier les opérations à l’intérieur du conteneur afin de garantir la cohérence de l’environnement, et à améliorer l’efficacité de la compilation grâce à la relecture des résultats de compilation et à l’utilisation d’outils de cache de compilation pour mettre en cache les résultats intermédiaires de la compilation.
Les pratiques mentionnées ci-dessus ont réduit le temps de compilation du projet de 60 % en moyenne, améliorant considérablement l’efficacité globale de l’intégration du code. À l’avenir, nous continuerons à paralléliser la compilation entre les composants et au sein de ceux-ci afin de réduire davantage les temps de compilation.
Les sources suivantes ont été utilisées pour cet article :
- “Découpler les arborescences sources en composants de niveau build”
- “Facteurs à prendre en compte lors de l’ajout de dépendances tierces à un projet”
- “Survivre aux dépendances logicielles”
- “Comprendre les dépendances : une étude des défis de coordination dans le développement logiciel”
À propos de l’auteur
Zhifeng Zhang est ingénieur DevOps senior chez Zilliz.com, travaillant sur Milvus, une base de données vectorielle open source, et instructeur autorisé de l’université des logiciels open source de la LF en Chine. Il a obtenu sa licence en Internet des objets (IOT) auprès du Software Engineering Institute of Guangzhou. Il consacre sa carrière à participer à des projets et à les diriger dans les domaines de CI/CD, DevOps, gestion de l’infrastructure informatique, boîte à outils Cloud-Native, conteneurisation et optimisation du processus de compilation.
Continuer à lire

Announcing the General Availability of Single Sign-On (SSO) on Zilliz Cloud
SSO is GA on Zilliz Cloud, delivering the enterprise-grade identity management capabilities your teams need to deploy vectorDB with confidence.

The Real Bottlenecks in Autonomous Driving — And How AI Infrastructure Can Solve Them
Autonomous driving faces a data bottleneck. Learn how AI-native vector databases like Zilliz solve scale, cost, and insight challenges across AV pipelines.

Optimizing Embedding Model Selection with TDA Clustering: A Strategic Guide for Vector Databases
Discover how Topological Data Analysis (TDA) reveals hidden embedding model weaknesses and helps optimize vector database performance.



