La ejecución dinámica es una función de Bazel desde la versión 0.21, en la que la ejecución local y remota de la misma acción se inician en paralelo, utilizando el resultado de la primera rama que finaliza y cancelando la otra rama. Combina la potencia de ejecución o la gran caché compartida de un sistema de compilación remoto con la baja latencia de la ejecución local, lo que proporciona lo mejor de ambos mundos para las compilaciones limpias e incrementales.
En esta página, se describe cómo habilitar, ajustar y depurar la ejecución dinámica. Si tienes configurada la ejecución local y remota, y estás intentando ajustar la configuración de Bazel para obtener un mejor rendimiento, esta página es para ti. Si aún no configuraste la ejecución remota, primero consulta la Descripción general de la ejecución remota de Bazel.
¿Habilitar la ejecución dinámica?
El módulo de ejecución dinámica forma parte de Bazel, pero para usar la ejecución dinámica, ya debes poder compilar de forma local y remota desde la misma configuración de Bazel.
Para habilitar el módulo de ejecución dinámica, pasa la marca --internal_spawn_scheduler
a Bazel. Esto agrega una nueva estrategia de ejecución llamada dynamic
. Ahora puedes usar esto como tu estrategia para las mnemónicas que deseas ejecutar de forma dinámica, como --strategy=Javac=dynamic
. Consulta la siguiente sección para saber qué mnemónicos elegir para habilitar la ejecución dinámica.
Para cualquier mnemónico que use la estrategia dinámica, las estrategias de ejecución remota se toman de la marca --dynamic_remote_strategy
y las estrategias locales, de la marca --dynamic_local_strategy
. Si pasas --dynamic_local_strategy=worker,sandboxed
, se establece el valor predeterminado para que la rama local de la ejecución dinámica intente usar trabajadores o la ejecución en zona de pruebas en ese orden. Si pasas --dynamic_local_strategy=Javac=worker
, se anula el valor predeterminado solo para la mnemotecnia de Javac. La versión remota funciona de la misma manera. Ambas marcas se pueden especificar varias veces. Si una acción no se puede ejecutar de forma local, se ejecuta de forma remota como de costumbre, y viceversa.
Si tu sistema remoto tiene una caché, la marca --local_execution_delay
agrega un retraso en milisegundos a la ejecución local después de que el sistema remoto haya indicado un acierto de caché. Esto evita que se ejecute la ejecución local cuando es probable que haya más aciertos de caché. El valor predeterminado es 1,000 ms, pero se debe ajustar para que sea un poco más largo de lo que suelen tardar los aciertos de caché. El tiempo real depende tanto del sistema remoto como del tiempo que tarda un viaje de ida y vuelta. Por lo general, el valor será el mismo para todos los usuarios de un sistema remoto determinado, a menos que algunos de ellos estén lo suficientemente lejos como para agregar latencia de ida y vuelta. Puedes usar las funciones de generación de perfiles de Bazel para ver cuánto tardan los aciertos de caché típicos.
La ejecución dinámica se puede usar con la estrategia local de zona de pruebas, así como con procesos de trabajo persistentes. Los trabajadores persistentes se ejecutarán automáticamente con el aislamiento de zona de pruebas cuando se usen con la ejecución dinámica y no podrán usar trabajadores de multiplexación. En los sistemas Darwin y Windows, la estrategia de zona de pruebas puede ser lenta. Puedes pasar --reuse_sandbox_directories
para reducir la sobrecarga de la creación de zonas de pruebas en estos sistemas.
La ejecución dinámica también se puede ejecutar con la estrategia standalone
, aunque, dado que la estrategia standalone
debe tomar el bloqueo de salida cuando comienza a ejecutarse, bloquea de manera efectiva la estrategia remota para que no termine primero. La marca --experimental_local_lockfree_output
permite evitar este problema, ya que permite que la ejecución local escriba directamente en el resultado, pero la ejecución remota la anula si termina primero.
Si una de las ramas de la ejecución dinámica finaliza primero, pero falla, toda la acción falla. Esta es una elección intencional para evitar que pasen desapercibidas las diferencias entre la ejecución local y la remota.
Para obtener más información sobre cómo funciona la ejecución dinámica y su bloqueo, consulta las excelentes entradas de blog de Julio Merino.
¿Cuándo debo usar la ejecución dinámica?
La ejecución dinámica requiere algún tipo de sistema de ejecución remota. Actualmente, no es posible usar un sistema remoto solo de caché, ya que una falta de caché se consideraría una acción fallida.
No todos los tipos de acciones son adecuados para la ejecución remota. Los mejores candidatos son aquellos que son inherentemente más rápidos de forma local, por ejemplo, a través del uso de trabajadores persistentes, o aquellos que se ejecutan lo suficientemente rápido como para que la sobrecarga de la ejecución remota domine el tiempo de ejecución. Dado que cada acción ejecutada de forma local bloquea una cierta cantidad de recursos de CPU y memoria, ejecutar acciones que no entran en esas categorías solo retrasa la ejecución de las que sí entran.
A partir de la versión 5.0.0-pre.20210708.4, el registro del rendimiento contiene datos sobre la ejecución de trabajadores, incluido el tiempo que se tarda en finalizar una solicitud de trabajo después de perder una carrera de ejecución dinámica. Si ves que los subprocesos de trabajadores de ejecución dinámica dedican mucho tiempo a adquirir recursos o mucho tiempo en async-worker-finish
, es posible que tengas algunas acciones locales lentas que retrasen los subprocesos de trabajadores.
En el perfil anterior, que usa 8 trabajadores de Javac, vemos que muchos trabajadores de Javac perdieron las carreras y terminaron su trabajo en los subprocesos de async-worker-finish
. Esto se debió a que una mnemónica no de trabajador consumió suficientes recursos como para retrasar a los trabajadores.
Cuando solo se ejecuta Javac con la ejecución dinámica, solo alrededor de la mitad de los trabajadores que se iniciaron terminan perdiendo la carrera después de comenzar su trabajo.
La marca --experimental_spawn_scheduler
que se recomendaba anteriormente dejó de estar disponible.
Activa la ejecución dinámica y establece dynamic
como la estrategia predeterminada para todas las mnemónicas, lo que a menudo generaría este tipo de problemas.
Solución de problemas
Los problemas con la ejecución dinámica pueden ser sutiles y difíciles de depurar, ya que solo se pueden manifestar en algunas combinaciones específicas de ejecución local y remota.
--debug_spawn_scheduler
agrega información adicional del sistema de ejecución dinámica que puede ayudar a depurar estos problemas. También puedes ajustar la marca --local_execution_delay
y la cantidad de trabajos remotos en comparación con los locales para facilitar la reproducción de los problemas.
Si tienes problemas con la ejecución dinámica usando la estrategia standalone
, intenta ejecutar sin --experimental_local_lockfree_output
o ejecuta tus acciones locales en un entorno de pruebas. Esto puede ralentizar un poco tu compilación (consulta la información anterior si usas Mac o Windows), pero elimina algunas posibles causas de errores.