Desglose del rendimiento de la compilación

Informar un problema . Ver fuente . Por la noche · 7.2 · 7.1 · 7.0 · 6.5 · 6.4

Bazel es complejo y realiza muchas acciones diferentes en el transcurso de una compilación, algunas de las cuales pueden afectar el rendimiento de la compilación. Esta página intenta asignar algunos de estos conceptos de Bazel a sus implicaciones en el rendimiento de la compilación. Mientras que no es exhaustiva, incluimos algunos ejemplos de cómo detectar el rendimiento de la compilación problemas a través de la extracción de métricas y lo que puedes hacer para solucionarlos. Con esto, esperamos que puedas aplicar estos conceptos cuando investigues regresiones de rendimiento de compilación.

Compilaciones limpias vs. compilaciones incrementales

Una compilación limpia es aquella que compila todo desde cero, mientras que una compilación compilación reutiliza algunos trabajos ya completados.

Sugerimos analizar las compilaciones incrementales y limpias por separado, en especial, cuando recopilas o agregas métricas que dependen del estado de Cachés de Bazel (por ejemplo, métricas de tamaño de las solicitudes de compilación ). También representan dos experiencias de usuario diferentes. En comparación con los valores iniciales una compilación limpia desde cero (lo que tarda más debido a una caché en frío), un proceso incremental las compilaciones ocurren con mucha más frecuencia a medida que los desarrolladores iteran en el código (por lo general, más rápido, dado que la caché ya suele estar caliente).

Puedes usar el campo CumulativeMetrics.num_analyses en el BEP para clasificar compilaciones. Si es num_analyses <= 1, es una compilación limpia. De lo contrario, podemos ampliar categorizarlo como una compilación incremental; el usuario podría haber cambiado a diferentes marcas u objetivos diferentes, lo que causa una compilación limpia de forma efectiva. Cualquiera es probable que una definición más rigurosa de incrementalidad deba presentarse de una 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 compilación puede ser difícil debido a su naturaleza no determinista. de ciertas métricas (por ejemplo, el tiempo de CPU de Bazel o los tiempos de cola en un clúster). Puede ser útil usar métricas deterministas como sustitutos 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 la compilación rendimiento. Una compilación más grande podría representar más trabajo en el análisis y construir los grafos de compilación. El crecimiento orgánico de las compilaciones es natural a medida que se agregan/crean más dependencias y, por lo tanto, aumentan en complejidad y será cada vez más costoso crearlos.

Podemos dividir este problema en varias fases de compilación y usar lo siguiente como métricas sustitutas del trabajo realizado en cada fase:

  1. PackageMetrics.packages_loaded: Es la cantidad de paquetes que se cargaron de forma correcta. Aquí, una regresión representa más trabajo que se debe hacer para leer y analizar cada archivo de COMPILACIÓN adicional en la fase de carga.

    • A menudo, esto se debe a la adición de dependencias y a tener que cargar sus el cierre transitivo.
    • Usa query / cquery para encontrar en la que podrían haberse agregado nuevas dependencias.
  2. TargetMetrics.targets_configured: que representa la cantidad de objetivos y configurados en la compilación. Una regresión representa más trabajo en construir y recorrer el grafo de destino configurado.

    • A menudo, esto se debe a la adición de dependencias y a tener que construir el gráfico de su cierre transitivo.
    • Usa cquery para encontrar dónde se ingresa dependencias podría haberse agregado.
  3. 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. Nota también incluye las acciones sin usar que podrían no haberse ejecutado.

  4. ActionSummary.actions_executed: Es la cantidad de acciones ejecutadas, una la regresión representa directamente más trabajo en la ejecución de estas acciones.

    • El BEP escribe las estadísticas de acción. ActionData, que muestra los tipos de acciones más ejecutadas. De forma predeterminada, recopila los 20 tipos de acciones principales, pero puedes pasar los --experimental_record_metrics_for_all_mnemonics para recopilar estos datos en todos los tipos de acciones que se ejecutaron.
    • Esto debería ayudarte a determinar qué tipo de acciones se ejecutaron (adicionalmente).
  5. BuildGraphSummary.outputArtifactCount: La cantidad de artefactos que crea las acciones ejecutadas.

    • Si la cantidad de acciones ejecutadas no aumentó, es probable que se cambió la implementación de una regla.

Todas estas métricas se ven afectadas por el estado de la caché local, por lo que deberás debes asegurarte de que las compilaciones de las que extraes estas métricas compilaciones limpias.

Notamos que una regresión en cualquiera de estas métricas puede estar acompañada de en tiempo real, tiempo de CPU y uso de memoria.

Uso de recursos locales

Bazel consume una variedad de recursos en tu máquina local (tanto para analizar el grafo de compilación, controlar la ejecución y ejecutar acciones locales). puede afectar el rendimiento o la disponibilidad de tu máquina a la hora de realizar la compilar y otras tareas.

Tiempo transcurrido

Quizás las métricas más susceptibles al ruido (y pueden variar mucho de la compilación de construir) es tiempo; en particular: tiempo de pared, tiempo de CPU y tiempo del sistema. Puedes usa bazel-bench para obtener una comparativa para estas métricas y, con una cantidad suficiente de --runs, puedes aumentar la importancia estadística de tus mediciones.

  • El tiempo real es el tiempo real transcurrido.

    • Si solo disminuye el tiempo, te sugerimos recopilar un el perfil de seguimiento de JSON y buscar para ver las diferencias. De lo contrario, probablemente sería más eficiente investigar otras métricas regresivas, ya que podrían haber afectado el muro tiempo.
  • El tiempo de CPU es el tiempo que dedica la CPU a ejecutar el código de usuario.

    • Si el tiempo de CPU disminuye en dos confirmaciones del proyecto, te sugerimos recopilar un perfil de CPU de Starlark. Es probable que también debas usar --nobuild para restringir la compilación a la fase de análisis, ya que allí es donde la mayoría de se completó el trabajo pesado de la CPU.
  • El tiempo del sistema es el tiempo que dedica la CPU al kernel.

    • Si el tiempo del sistema disminuye, se correlaciona principalmente con la E/S cuando Bazel lee desde tu sistema de archivos.

Creación de perfiles de carga en todo el sistema

Con el --experimental_collect_load_average_in_profiler estándar que se introdujo en Bazel 6.0, el El Generador de perfiles de seguimiento de JSON recopila promedio de carga del sistema durante la invocación.

Perfil que incluye la carga promedio del sistema

Figura 1: Perfil que incluye la carga promedio del sistema.

Una carga alta durante una invocación de Bazel puede indicar que Bazel programa demasiadas acciones locales en paralelo para tu máquina. Te recomendamos analizar ajustando --local_cpu_resources y --local_ram_resources, especialmente en entornos de contenedores (al menos hasta #16512 está combinado).

Supervisa el uso de memoria de Bazel

Hay dos fuentes principales para obtener el uso de memoria de Bazel: info de Bazel y el BEP:

  • bazel info used-heap-size-after-gc: La cantidad de memoria usada en bytes después de una llamada a System.gc().

    • Banco Bazel ofrece comparativas para esta métrica.
    • Además, hay peak-heap-size, max-heap-size y used-heap-size y committed-heap-size (consulta documentación), pero están menos relevantes.
  • BEP MemoryMetrics.peak_post_gc_heap_size: El tamaño máximo del montón de JVM en bytes después de la recolección de elementos no utilizados (requiere configuración --memory_profile que intenta forzar una recolección de elementos no utilizados completa).

Una regresión en el uso de la memoria suele ser el resultado de una regresión en métricas de tamaño de las solicitudes de compilación, que, a menudo, se deben a la adición de dependencias o a un cambio en la regla para implementarlos.

Para analizar el uso de memoria de Bazel en un nivel más detallado, te recomendamos usar 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, significativamente (especialmente para lenguajes interpretados) su espacio en memoria puede ser problemático. Bazel recopila métricas sobre sus trabajadores, en particular, el El campo WorkerMetrics.WorkerStats.worker_memory_in_kb indica cuánta memoria que usan los trabajadores (por mnemotecnia).

El generador de perfiles de seguimiento de JSON también recopila el uso de memoria persistente del trabajador durante la invocación pasando el valor --experimental_collect_system_network_usage (nueva en Bazel 6.0).

Perfil que incluye el uso de memoria de los trabajadores

Figura 2: Perfil que incluye el uso de memoria de los trabajadores.

Disminuir el valor de --worker_max_instances (valor predeterminado 4) puede ayudar a reducir la cantidad de memoria que usan los trabajadores persistentes. Estamos trabajando activamente en lo que hace que el programador y el administrador de recursos de Bazel sean más inteligentes para que el ajuste se necesitarán con menos frecuencia en el futuro.

Supervisa el tráfico de red para compilaciones remotas

En la ejecución remota, Bazel descarga artefactos que se compilaron como resultado de en la ejecución de acciones. El ancho de banda de tu red puede afectar el rendimiento de tu compilación.

Si usas la ejecución remota para tus compilaciones, te recomendamos considerar para supervisar el tráfico de red durante la invocación Proto NetworkMetrics.SystemNetworkStats 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 de todo el sistema durante la compilación pasando la marca --experimental_collect_system_network_usage (nuevo en Bazel) 6.0).

Perfil que incluye el uso de red en todo el sistema

Figura 3: Perfil que incluye el uso de red en todo el sistema.

Un uso de red alto, pero bastante plano, cuando se usa la ejecución remota, puede indicar esa red es el cuello de botella en tu compilación. si todavía no la estás usando, considera activar Compilación sin los Bytes pasando --remote_download_minimal Esto acelerará tus compilaciones al evitar la descarga de artefactos intermedios innecesarios.

Otra opción es configurar una red local caché de disco para guardar ancho de banda de descarga.