Accelerare la compilazione di 2,5 volte con il disaccoppiamento delle dipendenze e la containerizzazione dei test
Il tempo di compilazione può essere aggravato da dipendenze interne ed esterne complesse che evolvono durante tutto il processo di sviluppo, nonché da cambiamenti negli ambienti di compilazione, come il sistema operativo o le architetture hardware. Di seguito sono riportati problemi comuni che si possono incontrare lavorando su progetti AI o MLOps su larga scala:
Compilazione eccessivamente lunga - L'integrazione del codice viene eseguita centinaia di volte al giorno. Con centinaia di migliaia di righe di codice presenti, anche una piccola modifica potrebbe comportare una compilazione completa che in genere richiede una o più ore.
Ambiente di compilazione complesso - Il codice del progetto deve essere compilato in ambienti diversi, che coinvolgono sistemi operativi diversi, come CentOS e Ubuntu, dipendenze sottostanti, come GCC, LLVM e CUDA, e architetture hardware. E la compilazione in un ambiente specifico normalmente potrebbe non funzionare in un ambiente diverso.
Dipendenze complesse - La compilazione del progetto coinvolge più di 30 dipendenze tra componenti e di terze parti. Lo sviluppo del progetto porta spesso a cambiamenti nelle dipendenze, causando inevitabilmente conflitti tra dipendenze. Il controllo delle versioni tra le dipendenze è così complesso che l'aggiornamento della versione delle dipendenze influenzerà facilmente altri componenti.
Il download delle dipendenze di terze parti è lento o non riesce - Ritardi di rete o librerie di dipendenze di terze parti instabili causano download lenti delle risorse o errori di accesso, influenzando seriamente l'integrazione del codice.
Disaccoppiando le dipendenze e implementando la containerizzazione dei test, siamo riusciti a ridurre il tempo medio di compilazione del 60% lavorando al progetto open-source di ricerca di similarità degli embeddings Milvus.
Disaccoppiare le dipendenze del progetto
La compilazione del progetto di solito coinvolge un gran numero di dipendenze di componenti interni ed esterni. Più dipendenze ha un progetto, più complesso diventa gestirle. Man mano che il software cresce, diventa più difficile e costoso modificare o rimuovere dipendenze, nonché identificare gli effetti di tali operazioni. È richiesta una manutenzione regolare durante tutto il processo di sviluppo per garantire che le dipendenze funzionino correttamente. Una manutenzione scarsa, dipendenze complesse o dipendenze difettose possono causare conflitti che rallentano o bloccano lo sviluppo. In pratica, questo può significare download delle risorse in ritardo, errori di accesso che incidono negativamente sull'integrazione del codice e altro ancora. Disaccoppiare le dipendenze del progetto può mitigare i difetti e ridurre il tempo di compilazione, accelerando i test di sistema ed evitando inutili rallentamenti nello sviluppo del software.
Pertanto, consigliamo di disaccoppiare le dipendenze del tuo progetto:
- Suddividere i componenti con dipendenze complesse
- Utilizzare repository diversi per la gestione delle versioni.
- Utilizzare file di configurazione per gestire informazioni sulle versioni, opzioni di compilazione, dipendenze, ecc.
- Aggiungere i file di configurazione alle librerie dei componenti in modo che vengano aggiornati man mano che il progetto itera.
Ottimizzazione della compilazione tra componenti — Recupera e compila il componente pertinente in base alle dipendenze e alle opzioni di compilazione registrate nei file di configurazione. Etichetta e impacchetta i risultati della compilazione binaria e i file manifest corrispondenti, quindi caricali nel tuo repository privato. Se non viene apportata alcuna modifica a un componente o ai componenti da cui dipende, riproduci i suoi risultati di compilazione in base ai file manifest. Per problemi come ritardi di rete o librerie di dipendenze di terze parti instabili, prova a configurare un repository interno o a utilizzare repository mirror.
Per ottimizzare la compilazione tra componenti:
1.Crea il grafo delle relazioni di dipendenza — Utilizza i file di configurazione nelle librerie dei componenti per creare il grafo delle relazioni di dipendenza. Utilizza la relazione di dipendenza per recuperare le informazioni sulla versione (Git Branch, Tag e Git commit ID) e le opzioni di compilazione e altro ancora sia dei componenti dipendenti upstream che downstream.
Figure 1.
2.Verifica delle dipendenze — Genera avvisi per dipendenze circolari, conflitti di versione e altri problemi che emergono tra i componenti.
3.Appiattimento delle dipendenze — Ordina le dipendenze tramite Depth First Search (DFS) e unisci in primo piano i componenti con dipendenze duplicate per formare un grafo delle dipendenze.
Figura 2.
4.Usa l’algoritmo MerkleTree per generare un hash (Root Hash) contenente le dipendenze di ciascun componente in base alle informazioni di versione, alle opzioni di compilazione e altro ancora. Combinato con informazioni come il nome del componente, l’algoritmo forma un tag univoco per ciascun componente.
Figura 3.
5.In base alle informazioni del tag univoco del componente, verifica se esiste un archivio di compilazione corrispondente nel repo privato. Se viene recuperato un archivio di compilazione, decomprimilo per ottenere il file manifest per la riproduzione; in caso contrario, compila il componente, marca i file oggetto di compilazione generati e il file manifest, e caricali nel repo privato.
Implementare ottimizzazioni della compilazione all’interno dei componenti — Scegli uno strumento di cache di compilazione specifico per il linguaggio per memorizzare nella cache i file oggetto compilati, quindi caricali e archiviarli nel tuo repository privato. Per la compilazione C/C++, scegli uno strumento di cache di compilazione come CCache per memorizzare nella cache i file intermedi di compilazione C/C++, quindi archivia la cache CCache locale dopo la compilazione. Tali strumenti di cache di compilazione memorizzano semplicemente nella cache i file di codice modificati uno per uno dopo la compilazione e copiano i componenti compilati del file di codice non modificato in modo che possano essere direttamente coinvolti nella compilazione finale. L’ottimizzazione della compilazione all’interno dei componenti include i seguenti passaggi:
- Aggiungi le dipendenze di compilazione necessarie al Dockerfile. Usa Hadolint per eseguire controlli di conformità sul Dockerfile per garantire che l’immagine sia conforme alle best practice di Docker.
- Esegui il mirroring dell’ambiente di compilazione in base alla versione sprint del progetto (versione + build), al sistema operativo e ad altre informazioni.
- Esegui il container dell’ambiente di compilazione mirrorato e trasferisci l’ID dell’immagine al container come variabile d’ambiente. Ecco un comando di esempio per ottenere l’ID dell’immagine: “docker inspect ‘ — type=image’ — format ‘{{.ID}}’ repository/build-env:v0.1-centos7”.
- Scegli lo strumento di cache di compilazione appropriato: entra nel tuo containter per integrare e compilare i tuoi codici e verifica nel tuo repository privato se esiste una cache di compilazione appropriata. Se sì, scaricala ed estraila nella directory specificata. Dopo che tutti i componenti sono stati compilati, la cache generata dallo strumento di cache di compilazione viene impacchettata e caricata nel tuo repository privato in base alla versione del progetto e all’ID dell’immagine.
Ulteriore ottimizzazione della compilazione
La nostra build iniziale occupa troppo spazio su disco e larghezza di banda di rete, e richiede molto tempo per il deployment, quindi abbiamo adottato le seguenti misure:
- Scegli l’immagine base più leggera per ridurre la dimensione dell’immagine, ad es. alpine, busybox, ecc.
- Riduci il numero di layer dell’immagine. Riutilizza le dipendenze il più possibile. Unisci più comandi con “&&”.
- Pulisci i prodotti intermedi durante la creazione dell’immagine.
- Usa la cache dell’immagine per creare l’immagine il più possibile.
Man mano che il nostro progetto continua a progredire, l’utilizzo del disco e le risorse di rete hanno iniziato a crescere rapidamente con l’aumentare della cache di compilazione, mentre alcune delle cache di compilazione sono sottoutilizzate. Abbiamo quindi apportato le seguenti modifiche:
Pulizia regolare dei file di cache — Controlla regolarmente il repository privato (usando script, ad esempio) e pulisci i file di cache che non sono cambiati da un po’ o che non sono stati scaricati molto.
Caching selettivo della compilazione — Memorizza nella cache solo le compilazioni che richiedono molte risorse e salta la memorizzazione nella cache delle compilazioni che non richiedono molte risorse.
Sfruttare i test containerizzati per ridurre gli errori, migliorare stabilità e affidabilità
I codici devono essere compilati in ambienti diversi, che coinvolgono una varietà di sistemi operativi (ad es. CentOS e Ubuntu), dipendenze sottostanti (ad es. GCC, LLVM e CUDA) e specifiche architetture hardware. Il codice che viene compilato correttamente in un ambiente specifico può fallire in un ambiente diverso. Eseguendo i test all’interno dei container, il processo di test diventa più rapido e più accurato.
La containerizzazione garantisce che l’ambiente di test sia coerente e che un’applicazione funzioni come previsto. L’approccio di test containerizzato confeziona i test come container di immagini e costruisce un ambiente di test realmente isolato. I nostri tester hanno trovato questo approccio piuttosto utile, il che ha finito per ridurre i tempi di compilazione fino al 60%.
Garantire un ambiente di compilazione coerente — Poiché i prodotti compilati sono sensibili ai cambiamenti dell’ambiente di sistema, possono verificarsi errori sconosciuti in sistemi operativi diversi. Dobbiamo etichettare e archiviare la cache del prodotto compilato in base ai cambiamenti nell’ambiente di compilazione, ma sono difficili da categorizzare. Quindi abbiamo introdotto la tecnologia di containerizzazione per uniformare l’ambiente di compilazione e risolvere tali problemi.
Conclusione
Analizzando le dipendenze del progetto, questo articolo introduce diversi metodi per l’ottimizzazione della compilazione tra i componenti e all’interno di essi, fornendo idee e best practice per costruire un’integrazione continua del codice stabile ed efficiente. Questi metodi hanno contribuito a risolvere la lenta integrazione del codice causata da dipendenze complesse, a uniformare le operazioni all’interno del container per garantire la coerenza dell’ambiente e a migliorare l’efficienza della compilazione tramite la riproduzione dei risultati della compilazione e l’uso di strumenti di cache della compilazione per memorizzare nella cache i risultati intermedi della compilazione.
Le pratiche sopra menzionate hanno ridotto il tempo di compilazione del progetto in media del 60%, migliorando notevolmente l’efficienza complessiva dell’integrazione del codice. In futuro, continueremo a parallelizzare la compilazione tra i componenti e all’interno di essi per ridurre ulteriormente i tempi di compilazione.
Le seguenti fonti sono state utilizzate per questo articolo:
- “Decoupling Source Trees into Build-Level Components”
- “Fattori da considerare quando si aggiungono dipendenze di terze parti a un progetto”
- “Sopravvivere alle dipendenze software”
- “Comprendere le dipendenze: uno studio sulle sfide di coordinamento nello sviluppo software”
Informazioni sull’autore
Zhifeng Zhang è un senior DevOps engineer presso Zilliz.com che lavora su Milvus, un database vettoriale open-source, e istruttore autorizzato della LF open-source software university in Cina. Ha conseguito la laurea triennale in Internet of Things (IOT) presso il Software Engineering Institute of Guangzhou. Trascorre la sua carriera partecipando e guidando progetti nell’ambito di CI/CD, DevOps, gestione dell’infrastruttura IT, toolkit Cloud-Native, containerizzazione e ottimizzazione del processo di compilazione.
Continua a leggere

Expanding Our Global Reach: Zilliz Cloud Launches in Azure Central India
Zilliz Cloud expands to Azure Central India. This new region helps customers meet compliance, reduce latency, and optimize cloud costs when building AI applications.

AI Agents Are Quietly Transforming E-Commerce — Here’s How
Discover how AI agents transform e-commerce with autonomous decision-making, enhanced product discovery, and vector search capabilities for today's retailers.

DeepSeek Always Busy? Deploy It Locally with Milvus in Just 10 Minutes—No More Waiting!
Learn how to set up DeepSeek-R1 on your local machine using Ollama, AnythingLLM, and Milvus in just 10 minutes. Bypass busy servers and enhance AI responses with custom data.



