🚀 Experimente o Zilliz Cloud, o Milvus totalmente gerenciado, gratuitamente—experimente um desempenho 10x mais rápido! Experimente Agora>>

milvus-logo
LFAI
  • Home
  • Blog
  • Acelerando a compilação 2,5 vezes com desacoplamento de dependência e teste de conteinerização

Acelerando a compilação 2,5 vezes com desacoplamento de dependência e teste de conteinerização

  • Engineering
May 28, 2021
Zhifeng Zhang

O tempo de compilação pode ser agravado por dependências internas e externas complexas que evoluem ao longo do processo de desenvolvimento, bem como por alterações nos ambientes de compilação, como o sistema operacional ou as arquiteturas de hardware. A seguir estão os problemas comuns que podem ser encontrados ao trabalhar em projetos de IA ou MLOps em grande escala:

Compilação proibitivamente longa - A integração de código é efectuada centenas de vezes por dia. Com centenas de milhares de linhas de código em vigor, mesmo uma pequena alteração pode resultar numa compilação completa que, normalmente, demora uma ou mais horas.

Ambiente de compilação complexo - O código do projeto tem de ser compilado em diferentes ambientes, que envolvem diferentes sistemas operativos, como o CentOS e o Ubuntu, dependências subjacentes, como o GCC, o LLVM e o CUDA, e arquitecturas de hardware. E a compilação num ambiente específico pode normalmente não funcionar num ambiente diferente.

Dependências complexas - A compilação de projectos envolve mais de 30 dependências entre componentes e de terceiros. O desenvolvimento do projeto conduz frequentemente a alterações nas dependências, causando inevitavelmente conflitos de dependências. O controlo de versões entre dependências é tão complexo que a atualização da versão das dependências afectará facilmente outros componentes.

O descarregamento de dependências de terceiros é lento ou falha - Atrasos na rede ou bibliotecas de dependências de terceiros instáveis causam lentidão no descarregamento de recursos ou falhas de acesso, afectando seriamente a integração do código.

Ao dissociar as dependências e implementar a contentorização de testes, conseguimos diminuir o tempo médio de compilação em 60% enquanto trabalhávamos no projeto open-source de pesquisa de semelhanças de embeddings Milvus.


Desacoplar as dependências do projeto

A compilação de projectos envolve normalmente um grande número de dependências de componentes internos e externos. Quanto mais dependências um projeto tiver, mais complexa se torna a sua gestão. À medida que o software cresce, torna-se mais difícil e dispendioso alterar ou remover dependências, bem como identificar os efeitos de o fazer. É necessária uma manutenção regular ao longo do processo de desenvolvimento para garantir que as dependências funcionam corretamente. Uma manutenção deficiente, dependências complexas ou dependências defeituosas podem causar conflitos que atrasam ou impedem o desenvolvimento. Na prática, isso pode significar atrasos no download de recursos, falhas de acesso que afetam negativamente a integração do código e muito mais. O desacoplamento das dependências do projeto pode atenuar os defeitos e reduzir o tempo de compilação, acelerando os testes do sistema e evitando atrasos desnecessários no desenvolvimento do software.

Por isso, recomendamos dissociar as dependências do seu projeto:

  • Dividir componentes com dependências complexas
  • Utilizar diferentes repositórios para a gestão de versões.
  • Utilize ficheiros de configuração para gerir informações sobre versões, opções de compilação, dependências, etc.
  • Adicione os ficheiros de configuração às bibliotecas de componentes, para que sejam actualizados à medida que o projeto é iterado.

Otimizaçãoda compilação entre componentes - Puxe e compile o componente relevante de acordo com as dependências e as opções de compilação registadas nos ficheiros de configuração. Marque e empacote os resultados da compilação binária e os ficheiros de manifesto correspondentes e, em seguida, carregue-os no seu repositório privado. Se nenhuma alteração for feita a um componente ou aos componentes dos quais ele depende, reproduza os resultados da compilação de acordo com os arquivos de manifesto. Para problemas como atrasos de rede ou bibliotecas de dependência de terceiros instáveis, tente configurar um repositório interno ou usar repositórios espelhados.

Para otimizar a compilação entre componentes:

1.Criar gráfico de relação de dependência - Use os arquivos de configuração nas bibliotecas de componentes para criar gráfico de relação de dependência. Utilize a relação de dependência para obter as informações de versão (Git Branch, Tag e Git commit ID) e as opções de compilação, entre outros, dos componentes dependentes a montante e a jusante.

1.png 1.png

2.verificar dependências - Gerar alertas para dependências circulares, conflitos de versão e outros problemas que surgem entre componentes.

3.Achatar dependências - Classifique as dependências por Depth First Search (DFS) e componentes de mesclagem frontal com dependências duplicadas para formar um gráfico de dependências.

2.png 2.png

4. usar o algoritmo MerkleTree para gerar um hash (Root Hash) contendo dependências de cada componente com base em informações de versão, opções de compilação e muito mais. Combinado com informações como o nome do componente, o algoritmo forma uma etiqueta exclusiva para cada componente.

3.png 3.png

5. com base nas informações da etiqueta exclusiva do componente, verifique se existe um arquivo de compilação correspondente no repositório privado. Se for recuperado um arquivo de compilação, descompacte-o para obter o ficheiro de manifesto para reprodução; caso contrário, compile o componente, marque os ficheiros de objectos de compilação e o ficheiro de manifesto gerados e carregue-os no repositório privado.


Implementar optimizações de compilação nos componentes - Escolha uma ferramenta de cache de compilação específica da linguagem para armazenar em cache os ficheiros de objectos compilados e carregue-os e armazene-os no seu repositório privado. Para a compilação de C/C++, escolha uma ferramenta de cache de compilação como o CCache para armazenar em cache os arquivos intermediários de compilação de C/C++ e, em seguida, arquive o cache local do CCache após a compilação. Essas ferramentas de cache de compilação simplesmente armazenam em cache os arquivos de código alterados um a um após a compilação e copiam os componentes compilados do arquivo de código inalterado para que eles possam estar diretamente envolvidos na compilação final. A otimização da compilação nos componentes inclui as seguintes etapas:

  1. Adicionar as dependências de compilação necessárias ao Dockerfile. Use o Hadolint para executar verificações de conformidade no Dockerfile para garantir que a imagem esteja em conformidade com as práticas recomendadas do Docker.
  2. Espelhe o ambiente de compilação de acordo com a versão do sprint do projeto (versão + compilação), o sistema operacional e outras informações.
  3. Execute o contêiner do ambiente de compilação espelhado e transfira a ID da imagem para o contêiner como uma variável de ambiente. Aqui está um exemplo de comando para obter o ID da imagem: "docker inspect ' - type=image' - format '{{.ID}}' repository/build-env:v0.1-centos7".
  4. Escolha a ferramenta de cache de compilação adequada: Introduza o seu containter para integrar e compilar os seus códigos e verifique no seu repositório privado se existe uma cache de compilação adequada. Em caso afirmativo, descarregue-a e extraia-a para o diretório especificado. Depois de todos os componentes serem compilados, a cache gerada pela ferramenta de cache de compilação é empacotada e carregada no seu repositório privado com base na versão do projeto e na ID da imagem.


Otimização adicional da compilação

A nossa compilação inicial ocupa demasiado espaço em disco e largura de banda de rede, e demora muito tempo a ser implementada, pelo que tomámos as seguintes medidas:

  1. Escolher a imagem de base mais simples para reduzir o tamanho da imagem, por exemplo, alpine, busybox, etc.
  2. Reduzir o número de camadas de imagem. Reutilizar as dependências o máximo possível. Junte vários comandos com "&&".
  3. Limpar os produtos intermédios durante a construção da imagem.
  4. Utilizar a cache de imagem para construir a imagem tanto quanto possível.

À medida que o nosso projeto continua a progredir, a utilização do disco e os recursos de rede começaram a subir à medida que a cache de compilação aumentava, enquanto algumas das caches de compilação eram subutilizadas. Fizemos então os seguintes ajustes:

Limparregularmente os ficheiros de cache - Verificar regularmente o repositório privado (utilizando scripts, por exemplo) e limpar os ficheiros de cache que não foram alterados durante algum tempo ou que não foram descarregados muitas vezes.

Cache de compilação seletivo - Armazene em cache apenas as compilações que exigem muitos recursos e ignore as compilações em cache que não exigem muitos recursos.


Aproveitar os testes em contêineres para reduzir erros, melhorar a estabilidade e a confiabilidade

Os códigos têm de ser compilados em diferentes ambientes, que envolvem uma variedade de sistemas operativos (por exemplo, CentOS e Ubuntu), dependências subjacentes (por exemplo, GCC, LLVM e CUDA) e arquitecturas de hardware específicas. O código que compila com sucesso em um ambiente específico falha em um ambiente diferente. Ao executar testes dentro de contêineres, o processo de teste se torna mais rápido e mais preciso.

A conteinerização garante que o ambiente de teste seja consistente e que um aplicativo esteja funcionando conforme o esperado. A abordagem de teste em contêineres empacota os testes como contêineres de imagem e cria um ambiente de teste verdadeiramente isolado. Nossos testadores acharam essa abordagem bastante útil, o que acabou reduzindo os tempos de compilação em até 60%.

Garantir um ambiente de compilação consistente - Como os produtos compilados são sensíveis a alterações no ambiente do sistema, podem ocorrer erros desconhecidos em diferentes sistemas operativos. Temos de marcar e arquivar a cache de produtos compilados de acordo com as alterações no ambiente de compilação, mas são difíceis de categorizar. Por isso, introduzimos a tecnologia de contentorização para unificar o ambiente de compilação e resolver estes problemas.


Conclusão

Ao analisar as dependências do projeto, este artigo apresenta diferentes métodos para otimização da compilação entre e dentro dos componentes, fornecendo ideias e melhores práticas para criar uma integração de código contínua estável e eficiente. Esses métodos ajudaram a resolver a lentidão da integração de código causada por dependências complexas, unificar operações dentro do contêiner para garantir a consistência do ambiente e melhorar a eficiência da compilação por meio da reprodução dos resultados da compilação e do uso de ferramentas de cache de compilação para armazenar em cache os resultados intermediários da compilação.

Estas práticas acima mencionadas reduziram o tempo de compilação do projeto em 60%, em média, melhorando consideravelmente a eficiência global da integração do código. No futuro, continuaremos a paralelizar a compilação entre e dentro dos componentes para reduzir ainda mais os tempos de compilação.


As seguintes fontes foram usadas para este artigo:


Sobre o autor

Zhifeng Zhang é um engenheiro DevOps sénior na Zilliz.com que trabalha no Milvus, uma base de dados vetorial de código aberto, e instrutor autorizado da universidade de software de código aberto LF na China. Ele recebeu seu diploma de bacharel em Internet das Coisas (IOT) do Instituto de Engenharia de Software de Guangzhou. Ele passa sua carreira participando e liderando projetos na área de CI/CD, DevOps, gerenciamento de infraestrutura de TI, kit de ferramentas Cloud-Native, conteinerização e otimização de processos de compilação.

    Try Managed Milvus for Free

    Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.

    Get Started

    Like the article? Spread the word

    Continue Lendo