Execução dinâmica

Informar um problema Ver fonte Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

A execução dinâmica é um recurso do Bazel em que a execução local e remota da mesma ação é iniciada em paralelo, usando a saída da primeira ramificação que termina, cancelando a outra ramificação. Ele combina o poder de execução e/ou o grande cache compartilhado de um sistema de build remoto com a baixa latência da execução local, oferecendo o melhor dos dois mundos para builds limpos e incrementais.

Nesta página, descrevemos como ativar, ajustar e depurar a execução dinâmica. Se você tiver a execução local e remota configuradas e estiver tentando ajustar as configurações do Bazel para melhorar a performance, esta página é para você. Se você ainda não tiver a execução remota configurada, acesse a Visão geral da execução remota do Bazel.

Você está ativando a execução dinâmica?

O módulo de execução dinâmica faz parte do Bazel, mas para usar a execução dinâmica, você já precisa conseguir compilar localmente e remotamente na mesma configuração do Bazel.

Para ativar o módulo de execução dinâmica, transmita a flag --internal_spawn_scheduler para o Bazel. Isso adiciona uma nova estratégia de execução chamada dynamic. Agora você pode usar isso como estratégia para as mnemônicos que quer executar dinamicamente, como --strategy=Javac=dynamic. Consulte a próxima seção para saber como escolher quais mnemônicos ativar a execução dinâmica.

Para qualquer mnemônico que use a estratégia dinâmica, as estratégias de execução remota são extraídas da flag --dynamic_remote_strategy, e as estratégias locais da flag --dynamic_local_strategy. Transmitir --dynamic_local_strategy=worker,sandboxed define o padrão para a ramificação local da execução dinâmica para tentar com workers ou execução em sandbox nessa ordem. A transmissão de --dynamic_local_strategy=Javac=worker substitui o padrão apenas para o mnemônico do Javac. A versão remota funciona da mesma forma. As duas flags podem ser especificadas várias vezes. Se uma ação não puder ser executada localmente, ela será executada remotamente como de costume, e vice-versa.

Se o sistema remoto tiver um cache, a flag --dynamic_local_execution_delay vai adicionar um atraso em milissegundos à execução local depois que o sistema remoto indicar um acerto de cache. Isso evita a execução local quando é provável que haja mais acertos de cache. O valor padrão é 1.000 ms, mas precisa ser ajustado para ser um pouco maior do que o tempo normal das ocorrências em cache. O tempo real depende do sistema remoto e de quanto tempo leva uma viagem de ida e volta. Normalmente, o valor é o mesmo para todos os usuários de um determinado sistema remoto, a menos que alguns deles estejam longe o suficiente para adicionar latência de ida e volta. Use os recursos de criação de perfil do Bazel para analisar quanto tempo leva um acerto de cache típico.

A execução dinâmica pode ser usada com a estratégia de sandbox local e com workers persistentes. Os workers persistentes são executados automaticamente com sandboxing quando usados com execução dinâmica e não podem usar workers de multiplexação. Em sistemas Darwin e Windows, a estratégia de sandbox pode ser lenta. É possível transmitir --reuse_sandbox_directories para reduzir a sobrecarga da criação de sandboxes nesses sistemas.

A execução dinâmica também pode ser feita com a estratégia standalone. No entanto, como ela precisa usar o bloqueio de saída ao começar a execução, ela impede que a estratégia remota seja concluída primeiro.standalone A flag --experimental_local_lockfree_output permite contornar esse problema permitindo que a execução local grave diretamente na saída, mas seja interrompida pela execução remota, caso ela termine primeiro.

Se uma das ramificações da execução dinâmica terminar primeiro, mas for uma falha, toda a ação vai falhar. Essa é uma escolha intencional para evitar que diferenças entre a execução local e remota passem despercebidas.

Para mais informações sobre como a execução dinâmica e o bloqueio funcionam, leia as excelentes postagens do blog de Julio Merino.

Quando devo usar a execução dinâmica?

A execução dinâmica requer alguma forma de sistema de execução remota. No momento, não é possível usar um sistema remoto somente de cache, já que uma falha de cache seria considerada uma ação com falha.

Nem todos os tipos de ações são adequados para execução remota. Os melhores candidatos são aqueles que são inerentemente mais rápidos localmente, por exemplo, com o uso de workers persistentes, ou aqueles que são executados rápido o suficiente para que a sobrecarga da execução remota domine o tempo de execução. Como cada ação executada localmente bloqueia uma quantidade de recursos de CPU e memória, executar ações que não se enquadram nessas categorias apenas atrasa a execução daquelas que se enquadram.

A partir da versão 5.0.0-pre.20210708.4, o criação de perfil de desempenho contém dados sobre a execução do worker, incluindo o tempo gasto para concluir uma solicitação de trabalho após perder uma disputa de execução dinâmica. Se você notar que as linhas de execução do worker de execução dinâmica estão gastando muito tempo adquirindo recursos ou muito tempo no async-worker-finish, talvez haja algumas ações locais lentas atrasando as linhas de execução do worker.

Dados de criação de perfil com desempenho ruim de execução dinâmica

No perfil acima, que usa oito workers do Javac, vemos muitos deles perdendo as disputas e concluindo o trabalho nas linhas de execução async-worker-finish. Isso foi causado por um mnemônico não worker que consumiu recursos suficientes para atrasar os workers.

Dados de criação de perfil com melhor desempenho de execução dinâmica

Quando apenas o Javac é executado com execução dinâmica, apenas cerca de metade dos workers iniciados acaba perdendo a disputa depois de começar o trabalho.

A flag --experimental_spawn_scheduler recomendada anteriormente foi descontinuada. Ele ativa a execução dinâmica e define dynamic como a estratégia padrão para todos os mnemônicos, o que geralmente leva a esse tipo de problema.

Desempenho

A abordagem de execução dinâmica pressupõe que há recursos suficientes disponíveis localmente e remotamente para valer a pena gastar alguns recursos extras para melhorar o desempenho geral. No entanto, o uso excessivo de recursos pode desacelerar o próprio Bazel ou a máquina em que ele é executado, ou ainda exercer uma pressão inesperada em um sistema remoto. Há várias opções para mudar o comportamento da execução dinâmica:

--dynamic_local_execution_delay atrasa o início de uma ramificação local por um número de milissegundos depois que a ramificação remota é iniciada, mas apenas se houver um acerto de cache remoto durante o build atual. Isso faz com que builds que se beneficiam do armazenamento em cache remoto não desperdicem recursos locais quando é provável que a maioria das saídas possa ser encontrada no cache. Dependendo da qualidade do cache, reduzir isso pode melhorar a velocidade de build, mas ao custo de usar mais recursos locais.

O --experimental_dynamic_local_load_factor é uma opção experimental de gerenciamento avançado de recursos. Ele usa um valor de 0 a 1, sendo que 0 desativa esse recurso. Quando definido como um valor acima de 0, o Bazel ajusta o número de ações programadas localmente quando há muitas ações aguardando para serem programadas. Definir como 1 permite que sejam programadas tantas ações quanto houver CPUs disponíveis (conforme --local_cpu_resources). Valores mais baixos definem o número de ações programadas para um número correspondente menor à medida que mais ações ficam disponíveis para execução. Isso pode parecer contraditório, mas com um bom sistema remoto, a execução local não ajuda muito quando muitas ações estão sendo executadas, e a CPU local é mais bem utilizada gerenciando ações remotas.

O --experimental_dynamic_slow_remote_time prioriza o início de ramificações locais quando a ramificação remota está em execução há pelo menos esse tempo. Normalmente, a ação programada mais recentemente tem prioridade, já que tem a maior chance de vencer a disputa. No entanto, se o sistema remoto às vezes travar ou demorar muito, isso pode fazer com que um build avance. Esse recurso não é ativado por padrão porque pode ocultar problemas com o sistema remoto que precisam ser corrigidos. Monitore a performance do sistema remoto se você ativar essa opção.

--experimental_dynamic_ignore_local_signals pode ser usado para permitir que a ramificação remota assuma o controle quando um spawn local é encerrado devido a um determinado sinal. Isso é útil principalmente com limites de recursos do worker (consulte --experimental_worker_memory_limit_mb, --experimental_worker_sandbox_hardening e --experimental_sandbox_memory_limit_mb), em que os processos do worker podem ser encerrados quando usam muitos recursos.

O perfil de rastreamento JSON contém vários gráficos relacionados ao desempenho que podem ajudar a identificar maneiras de melhorar a compensação entre desempenho e uso de recursos.

Solução de problemas

Os problemas com a execução dinâmica podem ser sutis e difíceis de depurar, já que podem se manifestar apenas em algumas combinações específicas de execução local e remota. O --debug_spawn_scheduler adiciona mais saída do sistema de execução dinâmica que pode ajudar a depurar esses problemas. Também é possível ajustar a flag --dynamic_local_execution_delay e o número de jobs remotos x locais para facilitar a reprodução dos problemas.

Se você estiver com problemas na execução dinâmica usando a estratégia standalone , tente executar sem --experimental_local_lockfree_output ou execute suas ações locais em sandbox. Isso pode deixar seu build um pouco mais lento (consulte acima se você estiver usando Mac ou Windows), mas remove algumas possíveis causas de falhas.