A 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 é concluída, cancelando a outra. Ele combina a capacidade de execução e/ou o cache compartilhado grande 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 semelhantes.
Esta página descreve como ativar, ajustar e depurar a execução dinâmica. Se você configurou a execução local e remota e está tentando ajustar as configurações do Bazel para melhorar o desempenho, esta página é para você. Se você ainda não tiver configurado a execução remota, acesse a Visão geral da execução remota do Bazel.
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ê precisa ser capaz de compilar local 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 é possível
usar essa estratégia para os mnemônicos que você quer executar dinamicamente, como
--strategy=Javac=dynamic
. Consulte a próxima seção para saber como escolher quais mnemônicos
ativar para 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
tomadas da flag --dynamic_remote_strategy
, e as estratégias locais da
flag --dynamic_local_strategy
. A transmissão de
--dynamic_local_strategy=worker,sandboxed
define o padrão para a execução dinâmica
local a ser testada com workers ou execução em sandbox nessa
ordem. A transmissão de --dynamic_local_strategy=Javac=worker
substitui o padrão apenas para
a mnemônica Javac. A versão remota funciona da mesma forma. Ambas as flags podem
ser especificadas várias vezes. Se uma ação não puder ser executada localmente, ela será
executada remotamente normalmente e vice-versa.
Se o sistema remoto tiver um cache, a flag --dynamic_local_execution_delay
adiciona um atraso em milissegundos à execução local depois que o sistema remoto
indicar um acerto de cache. Isso evita a execução local quando mais acertos de cache
são prováveis. O valor padrão é 1000ms, mas precisa ser ajustado para ser um pouco
maior do que as ocorrências em cache geralmente levam. O tempo real depende do sistema
remoto e do tempo 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 longe o suficiente
para adicionar latência de ida e volta. Você pode usar os recursos de criação de perfil
do Bazel para saber quanto tempo as
acessões de cache típicas levam.
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
múltiplos. Em sistemas Darwin e Windows, a estratégia de sandbox
pode ser lenta. Você pode transmitir --reuse_sandbox_directories
para reduzir
o overhead 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 usar o bloqueio de saída quando começa a ser executada, ela
bloqueia efetivamente a estratégia remota para que ela não termine primeiro. A flag
--experimental_local_lockfree_output
permite contornar esse problema,
permitindo que a execução local seja gravada diretamente na saída, mas seja abortada pela
execução remota, caso ela termine primeiro.
Se uma das ramificações da execução dinâmica terminar primeiro, mas falhar, a ação inteira falhará. Essa é uma escolha intencional para evitar que as 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, consulte as excelentes postagens do blog de Julio Merino.
Quando devo usar a execução dinâmica?
A execução dinâmica requer algum tipo 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ção são adequados para execução remota. Os melhores candidatos são aqueles que são inerentemente mais rápidos localmente, por exemplo, pelo 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, a execução de ações que não se enquadram nessas categorias apenas atrasa a execução das que se enquadram.
A partir da versão
5.0.0-pre.20210708.4,
o cenário de desempenho contém dados
sobre a execução do worker, incluindo o tempo gasto para concluir uma solicitação de trabalho depois
de perder uma corrida de execução dinâmica. Se você notar que as linhas de execução de workers de execução dinâmica
gastam tempo significativo adquirindo recursos ou muito tempo no
async-worker-finish
, talvez algumas ações locais lentas estejam atrasando as linhas de execução
do worker.
No perfil acima, que usa oito workers Javac, muitos workers Javac
perderam as corridas e terminaram o trabalho nas linhas de execução
async-worker-finish
. Isso foi causado por uma mnemônica não relacionada a workers que consumiu recursos suficientes para
atrasar os workers.
Quando apenas o Javac é executado com execução dinâmica, apenas cerca de metade dos workers iniciais acabam perdendo a corrida depois de iniciar 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 que valha a pena gastar alguns recursos extras para melhorar o desempenho geral. No entanto, o uso excessivo de recursos pode desacelerar o 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 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 após a ramificação remota ter começado, mas somente se houver
um hit de cache remoto durante o build atual. Isso faz com que os builds que se beneficiam
do 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 com o custo de usar mais recursos
locais.
--experimental_dynamic_local_load_factor
é uma opção experimental avançada de gerenciamento de recursos. Ele usa um valor de 0 a 1, sendo 0 a desativação desse recurso.
Quando definido como um valor acima de 0, o Bazel ajusta o número de
ações programadas localmente quando muitas ações estão aguardando
ser programadas. A configuração como 1 permite que tantas ações sejam programadas quanto CPUs
estão disponíveis (conforme --local_cpu_resources
). Valores mais baixos definem o número
de ações programadas para menos ações, à medida que um número maior de ações está
disponível para execução. Isso pode parecer contra-intuitivo, 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 é usada melhor para gerenciar ações remotas.
--experimental_dynamic_slow_remote_time
prioriza a inicialização de filiais locais
quando a filial remota está em execução há pelo menos esse tempo. Normalmente, a
ação programada mais recentemente tem prioridade, porque tem a maior chance de
ganhar a corrida, mas se o sistema remoto às vezes travar ou demorar muito,
isso pode fazer com que um build seja transferido. Essa opção não é ativada 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 o controle quando uma geração local sai devido a um determinado indicador. Isso é
útil principalmente com os limites de recursos do worker (consulte
--experimental_worker_memory_limit_mb
,
--experimental_worker_sandbox_hardening
e
--experimental_sandbox_memory_limit_mb
),
em que as operações do worker podem ser encerradas quando usam muitos recursos.
O perfil de rastro JSON contém vários gráficos relacionados ao desempenho que podem ajudar a identificar maneiras de melhorar o compromiso entre desempenho e uso de recursos.
Solução de problemas
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 uma saída extra 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 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 em modo sandbox. Isso pode deixar seu build um pouco mais lento (confira acima se
você estiver usando Mac ou Windows), mas remove algumas causas possíveis de falhas.