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 termina, cancelando a outra. Ele combina o poder de execução e/ou o grande cache compartilhado de um sistema de compilação remoto com a baixa latência da execução local, fornecendo o melhor dos dois mundos para compilações limpas e incrementais.
Esta página descreve como ativar, ajustar e depurar a execução dinâmica. Se você tiver as execuções locais e remotas configuradas e estiver tentando ajustar as configurações do Bazel para melhorar o desempenho, esta página é ideal para você. Se você ainda não tiver configurado a execução remota, acesse primeiro 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ê já precisa ser capaz de compilar localmente e remotamente a partir da 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 você pode
usá-la como a estratégia para as mneumônicas que quer executar dinamicamente, como
--strategy=Javac=dynamic
. Consulte a próxima seção para saber como escolher as mnemônicas para ativar a execução dinâmica.
Para qualquer mnemônica que use a estratégia dinâmica, as estratégias de execução remota são retiradas da sinalização --dynamic_remote_strategy
e as estratégias locais da sinalização --dynamic_local_strategy
. Passar --dynamic_local_strategy=worker,sandboxed
define o padrão para o branch local de execução dinâmica para tentar com workers ou a execução no sandbox nessa ordem. Passar --dynamic_local_strategy=Javac=worker
substitui o padrão apenas para o mnemônico 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 normalmente 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 indicar uma ocorrência em cache. Isso evita a execução local quando é provável que haja mais ocorrências em cache. O valor padrão é de 1.000 ms, mas deve ser ajustado para ser um pouco mais longo do que as ocorrências de cache geralmente demoram. O tempo real depende do sistema remoto e do tempo de retorno. Normalmente, o valor será o mesmo para todos os usuários de um determinado sistema remoto, a menos que alguns deles estejam suficientemente distantes para adicionar latência de ida e volta. É possível usar os recursos de criação de perfil do Bazel para ver quanto tempo leva normalmente as ocorrências em cache.
A execução dinâmica pode ser usada com a estratégia de sandbox local, bem como com os workers permanentes. Os workers permanentes serão executados automaticamente com sandbox quando usados com a execução dinâmica e não poderão usar multiplex workers. Nos sistemas Darwin e Windows, a estratégia no sandbox pode ser lenta. Transmita --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 usar o bloqueio de saída quando começar a execução, ela bloqueia efetivamente a conclusão da estratégia remota. A sinalização --experimental_local_lockfree_output
permite uma maneira de resolver esse problema, permitindo que a execução local grave diretamente na saída, mas seja cancelada pela execução remota, caso termine primeiro.
Se uma das ramificações da execução dinâmica terminar primeiro, mas apresentar falha, a ação inteira falhará. Essa é uma escolha intencional para evitar que as diferenças entre a execução local e a remota sejam ignoradas.
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 alguma forma de sistema de execução remota. No momento, não é possível usar um sistema remoto somente de cache, porque uma ausência no 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, por meio do uso de workers permanentes ou aqueles que são executados com rapidez suficiente e que a sobrecarga da execução remota domina 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 daquelas que fazem isso.
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 depois de perder uma corrida de execução dinâmica. Se você observar linhas de execução de workers de execução dinâmica
gastando muito tempo com recursos significativos ou muito tempo em
async-worker-finish
, é possível que algumas ações locais lentas atrasem as
linhas de execução de workers.
No perfil acima, que usa 8 workers de Java, vemos que muitos deles perderam as corridas e terminaram o trabalho nas linhas de execução async-worker-finish
. Isso foi causado por um mnemônico não worker que usa recursos suficientes para atrasar os workers.
Quando apenas a Javac é executada com a execução dinâmica, apenas metade dos workers iniciados acabam perdendo a corrida depois de iniciar o trabalho.
A sinalização --experimental_spawn_scheduler
recomendada anteriormente está obsoleta.
Ele ativa a execução dinâmica e define dynamic
como a estratégia padrão para todos os
mnemônicos, o que geralmente levaria a esses tipos de problemas.
Desempenho
A abordagem de execução dinâmica pressupõe que há recursos suficientes disponíveis localmente e remotamente que vale 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 colocar 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 início do branch remoto, mas somente se tiver ocorrido uma ocorrência de cache remoto durante a versão atual. Isso faz com que as versões que se beneficiam do armazenamento em cache remoto não desperdice recursos locais quando é provável que a maioria das saídas possa ser encontrada no cache. Dependendo da qualidade do cache, a redução pode melhorar a velocidade de compilação, mas prejudica o uso de mais recursos locais.
--experimental_dynamic_local_load_factor
é uma opção avançada de gerenciamento de recursos avançados. Ele usa 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 ações aguardam programação. Defini-la como 1 permite a realização de tantas ações quanto
há CPUs disponíveis (de acordo com --local_cpu_resources
). Valores menores definem o número
de ações programadas como menos correspondentes, já que um número maior de
ações está disponível para execução. Isso pode não parecer 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 é melhor gasta no gerenciamento de ações remotas.
--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 mais chances de vencer a corrida. No entanto, se o sistema remoto às vezes travar ou demorar muito, isso poderá fazer com que a versão continue. Essa opção não está ativada por padrão, porque pode ocultar problemas no 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 o branch remoto assuma quando um gerador local sai devido a um determinado sinal. Isso é
útil principalmente junto 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 os processos do worker podem ser encerrados quando eles usam muitos recursos.
O perfil de trace JSON contém vários gráficos relacionados ao desempenho que podem ajudar a identificar maneiras de melhorar a compensação de 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 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âmica que pode ajudar a depurar esses problemas. Também é possível ajustar a sinalização --dynamic_local_execution_delay
e o número de jobs remotos em comparação a 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 as ações locais no sandbox. Isso pode deixar sua versão um pouco lenta (veja acima, no Mac ou Windows), mas remove algumas possíveis causas de falhas.