Bazel es complejo y realiza muchas tareas diferentes durante una compilación, algunas de las cuales pueden tener un impacto en el rendimiento de la compilación. En esta página, se intenta asignar algunos de estos conceptos de Bazel a sus implicaciones en el rendimiento de la compilación. Si bien no es exhaustivo, incluimos algunos ejemplos de cómo detectar problemas de rendimiento de la compilación mediante la extracción de métricas y qué puedes hacer para solucionarlos. Con esto, esperamos que puedas aplicar estos conceptos cuando investigues las regresiones de rendimiento de la compilación.
Compilaciones limpias vs. incrementales
Una compilación limpia es aquella que compila todo desde cero, mientras que una compilación incremental reutiliza parte del trabajo ya completado.
Te sugerimos que observes las compilaciones limpias e incrementales por separado, en especial cuando recopilas o agregas métricas que dependen del estado de las memorias caché de Bazel (por ejemplo, las métricas de tamaño de solicitud de compilación ). También representan dos experiencias de usuario diferentes. En comparación con iniciar una compilación limpia desde cero (que lleva más tiempo debido a una memoria caché fría), las compilaciones incrementales ocurren con mucha más frecuencia a medida que los desarrolladores iteran en el código (por lo general, más rápido, ya que la memoria caché suele estar activa).
Puedes usar el CumulativeMetrics.num_analyses campo en el BEP para clasificar
compilaciones. Si num_analyses <= 1, es una compilación limpia; de lo contrario, podemos categorizarla de manera general
como una compilación incremental. El usuario podría haber cambiado
a diferentes marcas o destinos, lo que provoca una compilación limpia de manera efectiva. Es probable que cualquier
definición más rigurosa de incrementalidad deba presentarse en forma
de heurística, por ejemplo, observar la cantidad de paquetes cargados
(PackageMetrics.packages_loaded).
Métricas de compilación deterministas como proxy para el rendimiento de la compilación
Medir el rendimiento de la compilación puede ser difícil debido a la naturaleza no determinista de ciertas métricas (por ejemplo, el tiempo de CPU de Bazel o los tiempos de cola en un clúster remoto ). Por lo tanto, puede ser útil usar métricas deterministas como proxy para la cantidad de trabajo que realiza Bazel, lo que, a su vez, afecta su rendimiento.
El tamaño de una solicitud de compilación puede tener una implicación significativa en el rendimiento de la compilación. Una compilación más grande podría representar más trabajo en el análisis y la construcción de los gráficos de compilación. El crecimiento orgánico de las compilaciones se produce de forma natural con el desarrollo, ya que se agregan o crean más dependencias y, por lo tanto, aumentan en complejidad y se vuelven más costosas de compilar.
Podemos dividir este problema en las distintas fases de compilación y usar las siguientes métricas como métricas de proxy para el trabajo realizado en cada fase:
PackageMetrics.packages_loaded: Es la cantidad de paquetes cargados correctamente. Una regresión aquí representa más trabajo que se debe realizar para leer y analizar cada archivo BUILD adicional en la fase de carga.TargetMetrics.targets_configured: Representa la cantidad de destinos y aspectos configurados en la compilación. Una regresión representa más trabajo en la construcción y el recorrido del gráfico de destino configurado.- Esto suele deberse a la adición de dependencias y a la necesidad de construir el gráfico de su cierre transitivo.
- Usa cquery para encontrar dónde se podrían haber agregado dependencias nuevas.
ActionSummary.actions_created: Representa las acciones creadas en la compilación, y una regresión representa más trabajo en la construcción del gráfico de acciones. Ten en cuenta que esto también incluye acciones sin usar que podrían no haberse ejecutado.- Usa aquery para depurar regresiones.
Te sugerimos que comiences con
--output=summaryantes de profundizar con--skyframe_state.
- Usa aquery para depurar regresiones.
Te sugerimos que comiences con
ActionSummary.actions_executed: Es la cantidad de acciones ejecutadas. Una regresión representa directamente más trabajo en la ejecución de estas acciones.- El BEP escribe las estadísticas de acción
ActionDataque muestran los tipos de acción más ejecutados. De forma predeterminada, recopila los 20 tipos de acción principales, pero puedes pasar--experimental_record_metrics_for_all_mnemonicspara recopilar estos datos para todos los tipos de acción que se ejecutaron. - Esto debería ayudarte a determinar qué tipo de acciones se ejecutaron (además).
- El BEP escribe las estadísticas de acción
BuildGraphSummary.outputArtifactCount: Es la cantidad de artefactos creados por las acciones ejecutadas.- Si la cantidad de acciones ejecutadas no aumentó, es probable que se haya cambiado una implementación de regla.
Todas estas métricas se ven afectadas por el estado de la memoria caché local, por lo que debes asegurarte de que las compilaciones de las que extraes estas métricas sean compilaciones limpias.
Observamos que una regresión en cualquiera de estas métricas puede ir acompañada de regresiones en el tiempo real, el tiempo de CPU y el uso de memoria.
Uso de recursos locales
Bazel consume una variedad de recursos en tu máquina local (tanto para analizar el gráfico de compilación y controlar la ejecución como para ejecutar acciones locales), lo que puede afectar el rendimiento o la disponibilidad de tu máquina para realizar la compilación y otras tareas.
Tiempo transcurrido
Quizás las métricas más susceptibles al ruido (y que pueden variar mucho de una compilación
a otra) sean el tiempo, en particular, el tiempo real, el tiempo de CPU y el tiempo del sistema. Puedes
usar bazel-bench para obtener
una comparativa de estas métricas y, con una cantidad suficiente de --runs, puedes
aumentar la importancia estadística de tu medición.
El tiempo real es el tiempo transcurrido en el mundo real.
- Si solo se produce una regresión en el tiempo real, te sugerimos que recopiles un perfil de seguimiento de JSON y busques diferencias. De lo contrario, sería más eficiente investigar otras métricas con regresión, ya que podrían haber afectado el tiempo real.
El tiempo de CPU es el tiempo que la CPU dedica a ejecutar el código del usuario.
- Si el tiempo de CPU regresa en dos confirmaciones de proyecto, te sugerimos que recopiles
un perfil de CPU de Starlark. Probablemente también deberías usar
--nobuildpara restringir la compilación a la fase de análisis, ya que es donde se realiza la mayor parte del trabajo pesado de la CPU.
- Si el tiempo de CPU regresa en dos confirmaciones de proyecto, te sugerimos que recopiles
un perfil de CPU de Starlark. Probablemente también deberías usar
El tiempo del sistema es el tiempo que la CPU dedica al kernel.
- Si el tiempo del sistema regresa, se correlaciona principalmente con la E/S cuando Bazel lee archivos de tu sistema de archivos.
Generación de perfiles de carga en todo el sistema
Con la
--experimental_collect_load_average_in_profiler
marca introducida en Bazel 6.0, el
generador de perfiles de seguimiento de JSON recopila el
promedio de carga del sistema durante la invocación.

Figura 1: Perfil que incluye el promedio de carga del sistema
Una carga alta durante una invocación de Bazel puede ser una indicación de que Bazel programa
demasiadas acciones locales en paralelo para tu máquina. Es posible que quieras ajustar
--local_cpu_resources
y --local_ram_resources,
en especial en entornos de contenedores (al menos hasta que
#16512 se combine).
Supervisión del uso de memoria de Bazel
Existen dos fuentes principales para obtener el uso de memoria de Bazel: Bazel info y el
BEP.
bazel info used-heap-size-after-gc: Es la cantidad de memoria usada en bytes después de una llamada aSystem.gc().- Bazel bench también proporciona comparativas para esta métrica.
- Además, existen
peak-heap-size,max-heap-size,used-heap-sizeycommitted-heap-size(consulta la documentación), pero son menos relevantes.
BEP’s
MemoryMetrics.peak_post_gc_heap_size: Es el tamaño máximo del montón de JVM en bytes después de la GC (requiere configurar--memory_profileque intenta forzar una GC completa).
Por lo general, una regresión en el uso de memoria es el resultado de una regresión en las métricas de tamaño de solicitud de compilación, que suelen deberse a la adición de dependencias o a un cambio en la implementación de la regla.
Para analizar la huella de memoria de Bazel en un nivel más detallado, te recomendamos que uses el generador de perfiles de memoria integrado para las reglas.
Generación de perfiles de memoria de trabajadores persistentes
Si bien los trabajadores persistentes pueden ayudar a acelerar las compilaciones
de manera significativa (en especial para los lenguajes interpretados), su huella de memoria puede
ser problemática. Bazel recopila métricas sobre sus trabajadores. En particular, el
WorkerMetrics.WorkerStats.worker_memory_in_kb campo indica cuánta memoria
usan los trabajadores (por mnemónico).
El generador de perfiles de seguimiento de JSON también
recopila el uso de memoria de los trabajadores persistentes durante la invocación pasando la
--experimental_collect_system_network_usage
marca (novedad en Bazel 6.0).

Figura 2: Perfil que incluye el uso de memoria de los trabajadores
Disminuir el valor de
--worker_max_instances
(4 de forma predeterminada) puede ayudar a reducir
la cantidad de memoria que usan los trabajadores persistentes. Estamos trabajando activamente en
que el administrador de recursos y el programador de Bazel sean más inteligentes, de modo que este ajuste preciso
se requiera con menos frecuencia en el futuro.
Supervisión del tráfico de red para compilaciones remotas
En la ejecución remota, Bazel descarga los artefactos que se compilaron como resultado de la ejecución de acciones. Por lo tanto, el ancho de banda de la red puede afectar el rendimiento de la compilación.
Si usas la ejecución remota para tus compilaciones, es posible que quieras considerar
supervisar el tráfico de red durante la invocación con el
NetworkMetrics.SystemNetworkStats proto de BEP
(requiere pasar --experimental_collect_system_network_usage).
Además, los perfiles de seguimiento de JSON
te permiten ver el uso de la red en todo el sistema durante la compilación
pasando la marca --experimental_collect_system_network_usage (novedad en Bazel
6.0).

Figura 3: Perfil que incluye el uso de la red en todo el sistema
Un uso de red alto, pero bastante plano, cuando se usa la ejecución remota puede indicar
que la red es el cuello de botella en tu compilación. Si aún no la usas,
considera activar la compilación sin los bytes pasando
--remote_download_minimal.
Esto acelerará tus compilaciones, ya que evitará la descarga de artefactos intermedios innecesarios.
Otra opción es configurar una memoria caché de disco local para ahorrar ancho de banda de descarga.