Ejecución dinámica

Informar un problema Ver fuente

La ejecución dinámica es una función en Bazel en la que la ejecución local y remota de la misma acción se inicia en paralelo, a través del resultado de la primera rama que finaliza y cancela la otra. 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 ofrece lo mejor de ambos mundos para compilaciones incrementales y limpias por igual.

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 la remota, y quieres 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 ve a la Descripción general de ejecución remota de Bazel.

¿Quieres habilitar la ejecución dinámica?

El módulo de ejecución dinámica es 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. Se agrega una estrategia de ejecución nueva llamada dynamic. Ahora, puedes usarlo como estrategia para los mnemotécnicos que desees ejecutar de forma dinámica, como --strategy=Javac=dynamic. Consulta la siguiente sección y aprende a elegir para qué mnemotécnicas habilitar la ejecución dinámica.

En el caso de cualquier uso mnemotécnico que use la estrategia dinámica, las estrategias de ejecución remota se toman de la marca --dynamic_remote_strategy y las estrategias locales se toman de la marca --dynamic_local_strategy. Pasar --dynamic_local_strategy=worker,sandboxed establece el valor predeterminado de la rama local de ejecución dinámica para probar con los trabajadores o la ejecución de la zona de pruebas en ese orden. Pasar --dynamic_local_strategy=Javac=worker anula el valor predeterminado solo para el valor mnemotécnico 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 manera local, lo hace de forma remota con normalidad y viceversa.

Si tu sistema remoto tiene una caché, la marca --dynamic_local_execution_delay agrega un retraso en milisegundos a la ejecución local después de que el sistema remoto indica un acierto de caché. De esta manera, se evita ejecutar una ejecución local cuando es probable que haya más aciertos de caché. El valor predeterminado es de 1,000 ms, pero se debe ajustar para que sea un poco más largo que lo que suelen tardar los aciertos de caché. El tiempo real depende tanto del sistema remoto como del tiempo 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 para agregar latencia de ida y vuelta. Puedes usar las funciones de creación de perfiles de Bazel para ver el tiempo que tardan los aciertos de caché típicos.

La ejecución dinámica se puede usar con la estrategia de zona de pruebas local y con trabajadores persistentes. Los trabajadores persistentes se ejecutarán automáticamente con zona de pruebas cuando se usen con ejecución dinámica y no podrán usar trabajadores multiplex. En los sistemas de Darwin y Windows, la estrategia de zona de pruebas puede ser lenta. Puedes pasar --reuse_sandbox_directories para reducir la sobrecarga de creación de zonas de pruebas en estos sistemas.

La ejecución dinámica también puede ejecutarse con la estrategia standalone; sin embargo, como la estrategia standalone debe tomar el bloqueo del resultado cuando comienza a ejecutarse, bloquea de forma efectiva la estrategia remota para que no finalice primero. La marca --experimental_local_lockfree_output permite solucionar este problema, ya que permite que la ejecución local escriba directamente en el resultado, pero que se anule con la ejecución remota, si esto finaliza primero.

Si una de las ramas de la ejecución dinámica finaliza primero, pero falla, toda la acción fallará. Esta es una decisión intencional para evitar que las diferencias entre la ejecución local y remota pasen desapercibidas.

Para obtener más información sobre el funcionamiento de 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. En la actualidad, no es posible usar un sistema remoto solo de caché, ya que un error de caché se consideraría una acción con errores.

No todos los tipos de acciones son adecuados para la ejecución remota. Los mejores candidatos son aquellos que son más rápidos de forma inherente a nivel local, por ejemplo, mediante el 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 cierta cantidad de recursos de CPU y memoria, ejecutar acciones que no se incluyen en esas categorías solo retrasa la ejecución de aquellas que sí lo hacen.

A partir de la versión 5.0.0-pre.20210708.4, la generación de perfiles de rendimiento contiene datos sobre la ejecución del trabajador, incluido el tiempo dedicado a finalizar una solicitud de trabajo después de perder una carrera de ejecución dinámica. Si observas que los subprocesos de trabajo de ejecución dinámica que pasan mucho tiempo adquiriendo recursos o mucho tiempo en async-worker-finish, es posible que tengas algunas acciones locales lentas que retrasen los subprocesos de trabajo.

Cómo generar perfiles de datos con un rendimiento de ejecución dinámico deficiente

En el perfil anterior, en el que se usan 8 trabajadores de Javac, vemos que muchos trabajadores de Javac perdieron las carreras y completaron su trabajo en los subprocesos async-worker-finish. Esto se debe a que un valor mnemotécnico que no es de trabajador toma suficientes recursos para demorar a los trabajadores.

Cómo generar perfiles de datos con un mejor rendimiento de ejecución dinámica

Cuando solo se ejecuta Javac con ejecución dinámica, solo alrededor de la mitad de los trabajadores iniciados terminan perdiendo la carrera después de comenzar su trabajo.

La marca --experimental_spawn_scheduler que se recomendó anteriormente dejó de estar disponible. Activa la ejecución dinámica y establece dynamic como la estrategia predeterminada para todos los mnemotécnicos, lo que a menudo generaría este tipo de problemas.

Rendimiento

El enfoque de ejecución dinámica supone que hay suficientes recursos disponibles de forma local y remota que vale la pena gastar algunos recursos adicionales para mejorar el rendimiento general. Sin embargo, el uso excesivo de recursos puede ralentizar Bazel o la máquina en la que se ejecuta, o ejercer una presión inesperada en un sistema remoto. Existen varias opciones para cambiar el comportamiento de la ejecución dinámica:

--dynamic_local_execution_delay retrasa el inicio de una rama local unos milisegundos después de que se inicia la rama remota, pero solo si hubo un acierto de caché remota durante la compilación actual. Esto hace que las compilaciones que se benefician del almacenamiento en caché remoto no desperdicien recursos locales cuando es probable que la mayoría de los resultados se puedan encontrar en la caché. Según la calidad de la caché, reducirlo podría mejorar las velocidades de compilación, a costa de usar más recursos locales.

--experimental_dynamic_local_load_factor es una opción experimental de administración avanzada de recursos. Se necesita un valor de 0 a 1, 0 para desactivar esta función. Cuando se establece en un valor superior a 0, Bazel ajusta la cantidad de acciones programadas de forma local cuando hay muchas acciones en espera para programarse. Establecerlo en 1 permite programar tantas acciones como haya CPU disponibles (según la --local_cpu_resources). Los valores más bajos establecen la cantidad de acciones programadas en menor medida a medida que haya una mayor cantidad de acciones disponible para ejecutarse. Esto puede sonar contradictorio, pero con un buen sistema remoto, la ejecución local no ayuda mucho cuando se ejecutan muchas acciones y es mejor dedicar la CPU local a administrar acciones remotas.

--experimental_dynamic_slow_remote_time prioriza el inicio de las ramas locales cuando la rama remota se ejecutó durante al menos este tiempo. Por lo general, la acción programada más reciente tiene prioridad, ya que tiene la mayor posibilidad de ganar la carrera, pero si el sistema remoto a veces se bloquea o tarda más tiempo, es posible que la compilación avance. Esta opción no está habilitada de forma predeterminada, ya que podría ocultar problemas con el sistema remoto que deberían solucionarse. Asegúrate de supervisar el rendimiento de tu sistema remoto si habilitas esta opción.

--experimental_dynamic_ignore_local_signals se puede usar para permitir que la rama remota tome el control cuando un generador local sale debido a una señal determinada. Esto es útil principalmente junto con los límites de recursos de trabajadores (consulta --experimental_worker_memory_limit_mb, --experimental_worker_sandbox_hardening y --experimental_sandbox_memory_limit_mb)), en los que los procesos trabajadores pueden finalizarse cuando usan demasiados recursos.

El perfil de seguimiento de JSON contiene una serie de gráficos relacionados con el rendimiento que pueden ayudar a identificar formas de mejorar el equilibrio entre el rendimiento y el uso de recursos.

Solución de problemas

Los problemas con la ejecución dinámica pueden ser sutiles y difíciles de depurar, ya que solo pueden manifestarse en algunas combinaciones específicas de ejecución local y remota. --debug_spawn_scheduler agrega una salida adicional del sistema de ejecución dinámico que puede ayudar a depurar estos problemas. También puedes ajustar la marca --dynamic_local_execution_delay y la cantidad de trabajos remotos o locales para facilitar la reproducción de los problemas.

Si tienes problemas con la ejecución dinámica con la estrategia standalone, intenta ejecutarlo sin --experimental_local_lockfree_output o ejecuta la zona de pruebas de tus acciones locales. Esto puede ralentizar un poco tu compilación (consulta más arriba si usas Mac o Windows), pero quita algunas causas posibles de fallas.