Execução dinâmica é um recurso do Bazel em que a execução local e remota da mesma ação são iniciadas em paralelo, usando a saída da primeira ramificação que termina, cancelando a outra. Ele combina a potência de execução e/ou grande cache compartilhado de um sistema de compilação remota com a baixa latência da execução local, oferecendo o melhor dos dois mundos para builds limpos e incrementais da mesma maneira.
Esta página descreve como ativar, ajustar e depurar a execução dinâmica. Se você tiver as execuções local e remota configuradas e estiver tentando ajustar as configurações do Bazel para melhorar o desempenho, esta página é para você. Se você ainda não configurou a execução remota, vá primeiro para a Visão geral da execução remota do Bazel.
Ativar 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 compilar local e remotamente a mesma configuração do Bazel.
Para ativar o módulo de execução dinâmica, transmita a sinalização --internal_spawn_scheduler
para o Bazel. Isso adiciona uma nova estratégia de execução chamada dynamic
. Agora é possível
usar isso como sua estratégia para as mneumônicas que você quer executar dinamicamente, como
--strategy=Javac=dynamic
. Consulte a próxima seção para saber como escolher para quais mnemônicas
ativar a execução dinâmica.
Para qualquer mnemônico que usa a estratégia dinâmica, as estratégias de execução remota são extraídas da sinalização --dynamic_remote_strategy
e as estratégias locais da sinalização --dynamic_local_strategy
. Transmitir
--dynamic_local_strategy=worker,sandboxed
define o padrão para a ramificação
local da execução dinâmica testar com workers ou a execução em sandbox,
nessa ordem. Transmitir --dynamic_local_strategy=Javac=worker
substitui o padrão somente para
mnemônicos Javac. A versão remota funciona da mesma forma. As duas sinalizações 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 sinalização --dynamic_local_execution_delay
adicionará um atraso em milissegundos à execução local depois que o sistema remoto tiver indicado uma ocorrência em cache. Isso evita executar a execução local quando houver mais probabilidades de ocorrência em cache. O valor padrão é 1.000 ms, mas é necessário ajustar para ser um pouco maior do que as ocorrências em cache geralmente levam. O tempo real depende do sistema
remoto e da duração de uma viagem de ida e volta. Normalmente, o valor será o mesmo para todos os usuários de um determinado sistema remoto, a menos que alguns deles estejam distantes o suficiente para aumentar a latência de ida e volta. Você pode usar os recursos de criação de perfil
do Bazel para ver quanto tempo típicas
as ocorrências em cache.
A execução dinâmica pode ser usada com a estratégia de sandbox local, bem como com
workers persistentes. Os workers persistentes são executados
automaticamente com sandbox quando usados com a execução dinâmica e não podem usar workers
multiplex. Nos sistemas Darwin e Windows, a estratégia de
sandbox pode ser lenta. Você pode transmitir --reuse_sandbox_directories
para reduzir
a sobrecarga da criação de sandboxes nesses sistemas.
A execução dinâmica também pode ser executada com a estratégia standalone
. No entanto, como a
estratégia standalone
precisa receber o bloqueio de saída quando começa a ser executada, ela
bloqueia efetivamente a estratégia remota de terminar primeiro. A sinalização --experimental_local_lockfree_output
apresenta uma maneira de contornar esse problema, permitindo que a execução local grave diretamente na saída, mas seja cancelada pela execução remota, caso isso termine primeiro.
Se uma das ramificações da execução dinâmica terminar primeiro, mas for uma falha, toda a ação falhará. Essa é uma escolha intencional para evitar que as diferenças entre as execuções local e remota passem despercebidas.
Para mais informações sobre como a execução dinâmica e o bloqueio dela funcionam, consulte 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 em cache, já que uma ausência no cache é 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 trabalhadores 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, a execução de ações que não se enquadram nessas categorias só atrasa a execução das que precisam.
A partir da versão
5.0.0-pre.20210708.4, a
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 corrida de execução dinâmica. Se houver linhas de execução de worker de execução dinâmica
que passam bastante tempo adquirindo recursos ou há muito tempo no
async-worker-finish
, é possível que algumas ações locais lentas atrasem as
linhas de execução de worker.
No perfil acima, que usa oito workers Javac, muitos workers Javac
perderam as corridas e concluem o trabalho nas linhas de execução
async-worker-finish
. Isso foi causado por um mnemônico que não é do worker tomando recursos suficientes para
atrasar os workers.
Quando apenas Javac é executado com execução dinâmica, apenas cerca de metade dos workers iniciados acaba perdendo a corrida depois de iniciar o trabalho.
A sinalização --experimental_spawn_scheduler
recomendada anteriormente foi descontinuada.
Ela ativa a execução dinâmica e define dynamic
como a estratégia padrão para todas
as mneumônicas, o que geralmente leva a esses tipos de problemas.
Performance
A abordagem de execução dinâmica pressupõe que há recursos suficientes disponíveis de maneira local e remota para que vale a pena gastar alguns recursos extras para melhorar o desempenho geral. No entanto, o uso excessivo de recursos pode atrasar o próprio Bazel ou a máquina em que ele é executado, ou colocar uma pressão inesperada em um sistema remoto. Há várias opções para alterar o comportamento da execução dinâmica:
--dynamic_local_execution_delay
atrasa o início de um branch local em alguns milissegundos após o branch remoto ser iniciado, mas somente se ocorrer uma ocorrência em cache remoto durante a criação atual. Dessa forma, as versões que se beneficiam
do armazenamento em cache remoto não desperdiçam recursos locais quando é provável que a maioria
das saídas possa ser encontrada no cache. Dependendo da qualidade do cache,
essa redução pode melhorar as velocidades de build, mas é preciso usar mais recursos
locais.
--experimental_dynamic_local_load_factor
é uma opção experimental de gerenciamento
avançado de recursos. É necessário um valor de 0 a 1, 0 desativando esse recurso.
Quando definido como um valor acima de 0, o Bazel ajusta o número de
ações programadas localmente quando muitas delas aguardam
ser programadas. Definir como 1 permite que tantas ações sejam programadas quanto
houver CPUs disponíveis (de acordo com a --local_cpu_resources
). Valores mais baixos definem o número
de ações programadas para um número menor correspondente, à medida que um número maior de ações fica
disponível para execução. Isso pode parecer pouco intuitivo, mas, com um bom sistema
remoto, a execução local não ajuda muito quando muitas ações estão sendo executadas, e
é melhor gastar a CPU local no gerenciamento de ações remotas.
--experimental_dynamic_slow_remote_time
prioriza o início de ramificações locais
quando a ramificação remota estiver em execução há pelo menos esse tempo. Normalmente, a
ação agendada mais recente tem prioridade, porque tem mais chance de
vencer a corrida, mas se o sistema remoto às vezes travar ou demorar muito,
isso pode fazer com que um build evolua. Esse recurso não é ativado por padrão porque
pode ocultar problemas com o sistema remoto que precisam ser corrigidos. Monitore
o desempenho 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 quando um spawn local sair devido a um determinado sinal. Isso é
útil principalmente com limites de recursos de 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 só podem
se manifestar sob algumas combinações específicas de execução local e remota.
O --debug_spawn_scheduler
adiciona uma saída extra do sistema de execução
dinâmico 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 e locais para
facilitar a reprodução dos problemas.
Se você tiver problemas com a execução dinâmica usando a estratégia standalone
,
tente executar sem --experimental_local_lockfree_output
ou execute
suas ações locais no sandbox. Isso pode deixar seu build um pouco mais lento (confira acima se
você estiver no Mac ou Windows), mas remove algumas causas possíveis para falhas.