Execução dinâmica

A execução dinâmica é um recurso do Bazel desde a versão 0.21 (link em inglês), 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 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.

Esta página descreve como ativar, ajustar e depurar a execução dinâmica. Se você tem a execução local e remota configurada e está 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, acesse primeiro 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. No entanto, para usar a execução dinâmica, você já precisa compilar local 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 usar isso como sua estratégia para as funções mnemônicas que quer executar dinamicamente, como --strategy=Javac=dynamic. Consulte a próxima seção para saber como escolher para quais mnemônicas a execução dinâmica será ativada.

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 são da sinalização --dynamic_local_strategy. Transmitir --dynamic_local_strategy=worker,sandboxed define o padrão para a ramificação local de execução dinâmica tentar com workers ou a execução em sandbox nessa ordem. Transmitir --dynamic_local_strategy=Javac=worker substitui o padrão apenas para mnemônico Javac. A versão remota funciona da mesma forma. Ambas as sinalizações podem ser especificadas várias vezes. Se não for possível executar uma ação localmente, ela será realizada remotamente, como normal, e vice-versa.

Se o sistema remoto tiver um cache, a flag --local_execution_delay vai 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 houver mais ocorrências em cache. O valor padrão é 1.000 ms, mas precisa ser ajustado para ser um pouco maior do que as ocorrências em cache normalmente 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 longe o suficiente para adicionar latência de ida e volta. Você pode usar os recursos de criação de perfil do Bazel para ver a duração típica de ocorrências em cache.

A execução dinâmica pode ser usada com a estratégia local em sandbox e com workers permanentes. Os workers persistentes são executados automaticamente com sandbox quando usados com a execução dinâmica e não podem usar workers de multiplex. Nos sistemas Darwin e Windows, a estratégia em sandbox pode ser lenta. É possível passar --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 ao começar a execução, 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, se isso terminar 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 execuções 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 alguma forma de sistema de execução remota. Atualmente, não é possível usar um sistema remoto somente de cache, já que 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 rápido o suficiente para 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 das demais.

A partir da versão 5.0.0-pre.20210708.4, a criação do perfil de desempenho contém dados sobre a execução do worker, incluindo o tempo gasto na conclusão de uma solicitação de trabalho após perder uma corrida de execução dinâmica. Se as linhas de execução de worker de execução dinâmicas passarem um tempo significativo adquirindo recursos ou muito tempo no async-worker-finish, talvez algumas ações locais lentas atrasem as linhas de execução de worker.

Criação de perfil de dados com baixo desempenho na execução dinâmica

No perfil acima, que usa oito workers Javac, vemos muitos workers Javac ter perdido as corridas e terminando seu trabalho nas linhas de execução async-worker-finish. Isso foi causado por um mnemônico não worker que tomava recursos suficientes para atrasar os workers.

Criação de perfil de dados 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 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 mnemônicas, o que geralmente leva a esses tipos de problemas.

Solução de problemas

Os problemas com a execução dinâmica podem ser sutis e difíceis de depurar, porque só podem se manifestar em algumas combinações específicas de execução local e remota. O --debug_spawn_scheduler adiciona outras saídas do sistema de execução dinâmica que podem ajudar a depurar esses problemas. Também é possível ajustar a flag --local_execution_delay e o número de jobs remotos vs. 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 executar as ações locais no sandbox. Isso pode deixar seu build um pouco mais lento (confira acima se você está no Mac ou Windows), mas remove algumas possíveis causas de falhas.