Ejecución dinámica

Informa un problema Ver código fuente

La ejecución dinámica es una función de Bazel en la que la ejecución local y remota de la misma acción se inician en paralelo, con el resultado de la primera rama que finaliza, y se cancela la otra rama. Combina la potencia de ejecución o la gran caché compartida de un sistema de compilación remota con la baja latencia de la ejecución local, lo que brinda lo mejor de ambos mundos para las compilaciones limpias y las incrementales por igual.

En esta página, se describe cómo habilitar, ajustar y depurar la ejecución dinámica. Si configuraste la ejecución local y la remota y tratas de ajustar la configuración de Bazel para obtener un mejor rendimiento, esta página es para ti. Si aún no lo has configurado, ve a la Descripción general de Remote Execution de Bazel.

¿Quieres 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 los mnemónicos que deseas ejecutar de forma dinámica, como --strategy=Javac=dynamic. Consulta la siguiente sección a fin de aprender a elegir los mnemónicos para habilitar la ejecución dinámica.

Para cualquier mnemotecnia que usa 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. Pasar --dynamic_local_strategy=worker,sandboxed establece el valor predeterminado de la rama local de ejecución dinámica para probar con trabajadores o ejecución de zona de pruebas en ese orden. Pasar --dynamic_local_strategy=Javac=worker anula el valor predeterminado solo para la notación mnemotécnica 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 lo hace normalmente, 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 la ejecución local cuando es probable que haya más aciertos de caché. El valor predeterminado es 1,000 ms, pero debe ajustarse para que sea un poco más largo que 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 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 de zona de pruebas local y con trabajadores persistentes. Los trabajadores persistentes se ejecutarán automáticamente con la zona de pruebas cuando se usen con la ejecución dinámica y no podrán usar los multiplex. En los sistemas Darwin y Windows, la estrategia de la zona de pruebas puede ser lenta. Puedes pasar --reuse_sandbox_directories a fin de 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, como la estrategia standalone debe tomar el bloqueo de salida cuando comienza a ejecutarse, bloquea de manera efectiva la finalización de la estrategia remota. 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 la ejecución remota anule, si es que debe finalizar primero.

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

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. Por el momento, 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 adecuadas para la ejecución remota. Los mejores candidatos son los que son inherentemente más rápidos a nivel local, por ejemplo, mediante el uso de trabajadores persistentes, o aquellos que se ejecutan con la rapidez suficiente de que la sobrecarga de la ejecución remota domina el tiempo de ejecución. Dado que cada acción ejecutada de forma local bloquea cierta cantidad de recursos de CPU y memoria, la ejecución de acciones que no se incluyen en esas categorías simplemente retrasa la ejecución de aquellas que sí lo hacen.

A partir de la versión 5.0.0-pre.20210708.4, la creación de perfiles de rendimiento contiene datos sobre la ejecución de los trabajadores, 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 invierten un tiempo significativo en la adquisición de recursos o mucho tiempo en el async-worker-finish, es posible que algunas acciones locales lentas demoren los subprocesos del trabajador.

Generación de perfiles de datos con bajo rendimiento de la ejecución dinámica

En el perfil anterior, que usa 8 trabajadores Javac, vemos que muchos trabajadores Javac perdieron las carreras y terminaron su trabajo en los subprocesos async-worker-finish. Esto se debe a que un trabajo mnemotécnico no tenía suficientes recursos para retrasar a los trabajadores.

Generación de perfiles de datos con un mejor rendimiento de la ejecución dinámica

Cuando solo se ejecuta Javac con ejecución dinámica, solo la mitad de los trabajadores iniciados pierden la carrera luego de iniciar 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 nemoté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, por lo 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 aplicar 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 una cantidad de milisegundos después de que se inicia la rama remota, pero solo si se produce 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 sea probable que la mayoría de los resultados se encuentren en la caché. Según la calidad de la caché, reducirla 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. Toma un valor de 0 a 1, 0, lo que desactiva esta función. Cuando se configura en un valor superior a 0, Bazel ajusta la cantidad de acciones programadas de manera local cuando muchas acciones están pendientes de programarse. Establecerlo en 1 permite que se programen tantas acciones como haya CPU disponibles (según --local_cpu_resources). Los valores más bajos establecen la cantidad de acciones programadas para menor cantidad, ya que hay una mayor cantidad de acciones disponibles para ejecutar. Esto puede sonar contraintuitivo, pero con un buen sistema remoto, la ejecución local no ayuda mucho cuando se ejecutan muchas acciones y la CPU local se ocupa mejor de administrar las acciones remotas.

--experimental_dynamic_slow_remote_time prioriza el inicio de ramas locales cuando la rama remota se estuvo ejecutando al menos durante este tiempo. Por lo general, la acción programada más reciente recibe prioridad, ya que tiene la mayor probabilidad de ganar la carrera, pero si el sistema remoto a veces se cuelga o toma mucho tiempo, esto puede generar una compilación. Esta opción no está habilitada de forma predeterminada, ya que podría ocultar los problemas del sistema remoto que debería solucionarse. Asegúrate de supervisar el rendimiento del sistema remoto si habilitas esta opción.

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

El perfil de seguimiento JSON contiene una serie de gráficos relacionados con el rendimiento que pueden ayudar a identificar formas de mejorar el equilibrio entre rendimiento y 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 con algunas combinaciones específicas de ejecución local y remota. El --debug_spawn_scheduler agrega 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 y locales para facilitar la reproducción de los problemas.

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