Ele é complexo e faz muitas coisas diferentes ao longo de um build, algumas delas podem afetar o desempenho do build. Nesta página, tentaremos associar alguns desses conceitos do Bazel às implicações deles no desempenho do build. Embora não seja extenso, incluímos alguns exemplos de como detectar problemas de desempenho do build por meio da extração de métricas e o que é possível fazer para corrigi-los. Com isso, esperamos que você possa aplicar esses conceitos ao investigar regressões de desempenho do build.
Builds limpos vs. incrementais
Um build limpo é aquele que cria tudo do zero, enquanto um build incremental reutiliza alguns trabalhos já concluídos.
Sugerimos analisar builds limpos e incrementais separadamente, especialmente quando você estiver coletando / agregando métricas que dependem do estado dos caches do Bazel (por exemplo, métricas de tamanho de solicitação de build ). Elas também representam duas experiências diferentes do usuário. Em comparação com iniciar um build limpo do zero (o que leva mais tempo devido a um cache frio), os builds incrementais acontecem com muito mais frequência, à medida que os desenvolvedores iteram no código. Normalmente, é mais rápido, porque o cache geralmente já está quente.
É possível usar o campo CumulativeMetrics.num_analyses
no BEP para classificar builds. Se for num_analyses <= 1
, significa que o build está limpo. Caso contrário, podemos
categorizá-lo amplamente como provavelmente um build incremental. O usuário pode ter alternado
para sinalizações ou destinos diferentes, causando um build efetivamente limpo. Qualquer definição mais rigorosa de incrementabilidade provavelmente terá que vir na forma de uma heurística, por exemplo, analisar o número de pacotes carregados (PackageMetrics.packages_loaded
).
Métricas deterministas de build como substitutos do desempenho do build
Medir o desempenho da compilação pode ser difícil devido à natureza não determinista de determinadas métricas (por exemplo, tempo de CPU do Bazel ou tempos de fila em um cluster remoto). Assim, pode ser útil usar métricas determinísticas como proxy da quantidade de trabalho feito pelo Bazel, o que, por sua vez, afeta o desempenho.
O tamanho de uma solicitação de build pode ter uma implicação significativa no desempenho do build. Um build maior pode gerar mais trabalho na análise e construção dos gráficos. O crescimento orgânico de builds ocorre naturalmente com o desenvolvimento, à medida que mais dependências são adicionadas/criadas, aumentando a complexidade e o custo de criação.
Podemos dividir esse problema nas várias fases de compilação e usar as seguintes métricas como métricas substitutas para o trabalho realizado em cada fase:
PackageMetrics.packages_loaded
: o número de pacotes carregados. Uma regressão aqui representa mais trabalho que precisa ser feito para ler e analisar cada arquivo BUILD adicional na fase de carregamento.TargetMetrics.targets_configured
: representa o número de destinos e aspectos configurados no build. Uma regressão representa mais trabalho na construção e na travessia do gráfico de destino configurado.- Geralmente, isso ocorre devido à adição de dependências e à necessidade de construir o gráfico do fechamento transitivo.
- Use cquery para descobrir onde novas dependências podem ter sido adicionadas.
ActionSummary.actions_created
: representa as ações criadas no build, e uma regressão representa mais trabalho na construção do gráfico de ações. Isso também inclui ações não usadas que podem não ter sido executadas.- Use aquery para depurar regressões.
Sugerimos começar com
--output=summary
antes de detalhar ainda mais com--skyframe_state
.
- Use aquery para depurar regressões.
Sugerimos começar com
ActionSummary.actions_executed
: o número de ações executadas. Uma regressão representa diretamente mais trabalho na execução dessas ações.- O BEP grava as estatísticas de ação
ActionData
que mostram os tipos de ação mais executados. Por padrão, ela coleta os 20 principais tipos de ação, mas é possível transmitir o--experimental_record_metrics_for_all_mnemonics
para coletar esses dados para todos os tipos de ação que foram executados. - Isso deve ajudar você a descobrir que tipo de ações foram executadas (além disso).
- O BEP grava as estatísticas de ação
BuildGraphSummary.outputArtifactCount
: o número de artefatos criados pelas ações executadas.- Se o número de ações executadas não aumentou, é provável que uma implementação de regra tenha sido alterada.
Essas métricas são afetadas pelo estado do cache local. Portanto, verifique se os builds das quais você extrai essas métricas são builds limpos.
Observamos que uma regressão em qualquer uma dessas métricas pode ser acompanhada por regressões no tempo decorrido, no tempo de CPU e no uso de memória.
Uso de recursos locais
O Bazel consome uma variedade de recursos na máquina local para analisar o gráfico de build e conduzir a execução e executar ações locais. Isso pode afetar o desempenho e a disponibilidade da máquina na execução da versão, além de outras tarefas.
Tempo gasto
Talvez as métricas mais suscetíveis ao ruído (e possam variar muito de build
para build) sejam o tempo decorrido, principalmente os tempos decorridos, de CPU e do sistema. Use o bazel-bench para ter um comparativo de mercado para essas métricas. Com um número suficiente de --runs
, é possível aumentar a significância estatística da medição.
O tempo decorrido é o tempo real decorrido.
- Para regressões apenas do tempo decorrido, sugerimos coletar um perfil de rastreamento JSON e procurar diferenças. Caso contrário, provavelmente seria mais eficiente investigar outras métricas regressivas, porque elas poderiam ter afetado o tempo decorrido.
Tempo de CPU é o tempo gasto pela CPU executando o código do usuário.
- Se o tempo de CPU regredir em duas confirmações do projeto, sugerimos coletar
um perfil de CPU do Starlark. Você provavelmente também precisa usar
--nobuild
para restringir o build à fase de análise, já que é nela que a maior parte do trabalho pesado da CPU é feita.
- Se o tempo de CPU regredir em duas confirmações do projeto, sugerimos coletar
um perfil de CPU do Starlark. Você provavelmente também precisa usar
O tempo do sistema é o tempo gasto pela CPU no kernel.
- Se o tempo do sistema regressa, isso é principalmente correlacionado com a E/S quando o Bazel lê arquivos do seu sistema.
Criação de perfil de carga em todo o sistema
Usando a sinalização
--experimental_collect_load_average_in_profiler
introduzida no Bazel 6.0, o
criador de perfil de rastreamento JSON coleta a
média de carga do sistema durante a invocação.
Figura 1. Perfil que inclui a média de carga do sistema.
Uma carga alta durante uma invocação do Bazel pode ser uma indicação de que ele programa
muitas ações locais em paralelo para sua máquina. Tente ajustar
--local_cpu_resources
e --local_ram_resources
,
especialmente em ambientes de contêiner (pelo menos até o
#16512 ser mesclado).
Como monitorar o uso de memória do Bazel
Há duas origens principais para receber o uso da memória do Bazel: a info
do Bazel e o
BEP.
bazel info used-heap-size-after-gc
: a quantidade de memória usada em bytes após uma chamada paraSystem.gc()
.- O Bazel bench também fornece comparativos de mercado para essa métrica.
- Além disso, existem
peak-heap-size
,max-heap-size
,used-heap-size
ecommitted-heap-size
(consulte a documentação), mas são menos relevantes.
BEP:
MemoryMetrics.peak_post_gc_heap_size
: tamanho do tamanho do heap de pico da JVM em bytes após a GC (requer a configuração--memory_profile
que tenta forçar uma GC completa).
Uma regressão no uso da memória geralmente é resultado de uma regressão nas métricas de tamanho da solicitação de build, que geralmente ocorrem devido à adição de dependências ou a uma mudança na implementação da regra.
Para analisar o consumo de memória do Bazel em um nível mais granular, recomendamos usar o Memory Profiler integrado para regras.
Criação de perfil de memória de workers permanentes
Os workers persistentes podem ajudar a acelerar as versões de maneira significativa (especialmente para linguagens interpretadas), mas o consumo de memória pode ser problemático. O Bazel coleta métricas dos workers. O campo
WorkerMetrics.WorkerStats.worker_memory_in_kb
informa quanta memória
os workers usam (por métodos mnemônicos).
O criador de perfil de trace JSON também
coleta o uso de memória do worker persistente durante a invocação passando a sinalização
--experimental_collect_system_network_usage
(nova no Bazel 6.0).
Figura 2. Perfil que inclui o uso de memória dos workers.
Diminuir o valor de
--worker_max_instances
(padrão 4) pode ajudar a diminuir
a quantidade de memória usada pelos workers permanentes. Estamos trabalhando ativamente para
tornar o gerenciador de recursos e o programador do Bazel mais inteligentes para que esses ajustes
sejam necessários com menos frequência no futuro.
Como monitorar o tráfego de rede para compilações remotas
Na execução remota, o Bazel faz o download de artefatos criados como resultado de ações de execução. Dessa forma, a largura de banda da rede pode afetar o desempenho do build.
Se você estiver usando a execução remota para seus builds, considere
monitorar o tráfego de rede durante a invocação usando o proto
NetworkMetrics.SystemNetworkStats
do BEP
(requer a transmissão de --experimental_collect_system_network_usage
).
Além disso, os perfis de rastreamento JSON
permitem ver o uso da rede em todo o sistema durante a compilação
transmitindo a flag --experimental_collect_system_network_usage
(nova no Bazel
6.0).
Figura 3. Perfil que inclui o uso de rede em todo o sistema.
Um uso de rede alto, mas simples, com a execução remota, pode indicar
que a rede é o gargalo no build. Se você ainda não a estiver usando,
ative o build sem os bytes transmitindo
--remote_download_minimal
.
Isso acelera os builds, evitando o download de artefatos intermediários desnecessários.
Outra opção é configurar um cache em disco local para economizar na largura de banda de download.