Trabajadores persistentes

Informar un problema Ver fuente Nightly · 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

En esta página, se explica cómo usar trabajadores persistentes, los beneficios, los requisitos y cómo los trabajadores afectan la zona de pruebas.

Un trabajador persistente es un proceso de larga duración iniciado por el servidor de Bazel, que funciona como un wrapper en la herramienta real (por lo general, un compilador) la herramienta en sí. Para beneficiarse de los trabajadores persistentes, la herramienta debe permite realizar una secuencia de compilaciones, y el wrapper debe traducir entre la API de la herramienta y el formato de solicitud/respuesta que se describe a continuación. Es igual. se puede llamar al trabajador con y sin la marca --persistent_worker en misma compilación y es responsable de iniciar y comunicarse adecuadamente con el y el cierre de los trabajadores al salir. A cada instancia de trabajador se le asigna (pero no transferido) a un directorio de trabajo independiente <outputBase>/bazel-workers

El uso de trabajadores persistentes es una estrategia de ejecución que disminuye la sobrecarga de inicio, permite más compilación JIT y habilita el almacenamiento en caché de, por ejemplo, los árboles de sintaxis abstracta en la ejecución de la acción. Esta estrategia logra estas mejoras enviando varias solicitudes a un proceso de larga duración.

Los trabajadores persistentes se implementan para varios lenguajes, incluido Java, Escala Kotlin y mucho más.

Los programas que usan un entorno de ejecución de NodeJS pueden usar la biblioteca auxiliar @bazel/worker para implementar el protocolo de trabajador.

Usa trabajadores persistentes

Bazel 0.27 y versiones posteriores usan trabajadores persistentes de forma predeterminada cuando se ejecutan compilaciones, aunque la ejecución remota tiene prioridad. En el caso de las acciones que no admiten trabajadores persistentes, Bazel recurre al inicio de una instancia de herramienta para cada acción. Puedes configurar tu compilación de forma explícita para que use trabajadores persistentes si estableces la estrategia worker para las mnemotecnias de herramientas aplicables. Como práctica recomendada, este ejemplo incluye especificar local como una resguardo de la estrategia worker:

bazel build //my:target --strategy=Javac=worker,local

Usar la estrategia de trabajadores en lugar de la estrategia local puede impulsar la compilación de manera significativa, según la implementación. En Java, las compilaciones pueden ser de 2 a 4 veces más rápidas, a veces más, para la compilación incremental. Compilar Bazel es aproximadamente 2.5 veces más rápido con los trabajadores. Para obtener más detalles, consulta la "Elige la cantidad de trabajadores" sección.

Si también tienes un entorno de compilación remoto que coincide con tu compilación local entorno, puedes usar el modelo estrategia dinámica, que ejecuta una ejecución remota y una de trabajador. Para habilitar la estrategia dinámica, pasa la marca --experimental_spawn_scheduler. Esta estrategia habilita automáticamente a los trabajadores, por lo que no es necesario especificar la estrategia worker, pero aún puedes usar local o sandboxed como y resguardos.

Elige la cantidad de trabajadores

El número predeterminado de instancias de trabajador por nombre mnemónico es 4, pero se puede ajustar. con el worker_max_instances marca. Existe una compensación entre hacer un buen uso de las CPU disponibles y el de compilación JIT y aciertos de caché que recibes. Con más trabajadores, más objetivos pagarán los costos iniciales de ejecutar código sin JIT y hacer clic en frío cachés. Si tienes que compilar una pequeña cantidad de destinos, un solo trabajador puede proporcionar el mejor equilibrio entre la velocidad de compilación y el uso de recursos (por ejemplo, consulta el error #8586. La marca worker_max_instances establece la cantidad máxima de instancias de trabajador por mnemónico y conjunto de marcas (consulta a continuación), por lo que, en un sistema mixto, podrías terminar usando bastante memoria si mantienes el valor predeterminado. Para compilaciones incrementales, el beneficio de varias instancias de trabajador es aún menor.

Este gráfico muestra los tiempos de compilación desde cero para Bazel (objetivo //src:bazel) en una estación de trabajo de Linux con hipersubproceso de Intel Xeon de 3.5 GHz de 6 núcleos con 64 GB de RAM. Para cada configuración de trabajador, se ejecutan cinco compilaciones limpias se toman el promedio de los últimos cuatro.

Gráfico de mejoras en el rendimiento de compilaciones limpias

Figura 1: Gráfico de mejoras en el rendimiento de compilaciones limpias.

Para esta configuración, dos trabajadores proporcionan la compilación más rápida, aunque solo con una mejora del 14% en comparación con un trabajador. Un trabajador es una buena opción si deseas usan menos memoria.

Por lo general, la compilación incremental beneficia aún más. Las compilaciones limpias son relativamente raras, pero es común cambiar un solo archivo entre compilaciones, en particular en el desarrollo impulsado por pruebas. El ejemplo anterior también tiene algunas acciones de empaquetado que no son de Java que pueden eclipsar el tiempo de compilación incremental.

Vuelve a compilar solo las fuentes de Java (//src/main/java/com/google/devtools/build/lib/bazel:BazelServer_deploy.jar) después de cambiar una constante de cadena interna AbstractContainerizingSandboxedSpawn.java ofrece una aceleración 3 veces mayor (un promedio de 20 compilaciones incrementales con una compilación de preparación) descartada):

Gráfico de mejoras en el rendimiento de las compilaciones incrementales

Figura 2: Gráfico de las mejoras de rendimiento de las compilaciones incrementales.

La aceleración depende del cambio que se realice. En la situación anterior, se mide una aceleración de un factor 6 cuando se cambia una constante de uso general.

Modifica trabajadores persistentes

Puedes pasar el --worker_extra_flag para especificar marcas de inicio a los trabajadores, con clave mnemotécnica. Por ejemplo, pasar --worker_extra_flag=javac=--debug activa la depuración solo para Javac. Solo se puede establecer una marca de trabajador por uso de esta marca y solo para una mnemónica. Los trabajadores no solo se crean por separado para cada mnemónica, sino también para las variaciones en sus marcas de inicio. Cada combinación de mnemónicos y marcas de inicio se combina en un WorkerKey y, para cada WorkerKey, se pueden crear hasta worker_max_instances trabajadores. Consulta la siguiente sección para descubrir cómo la configuración de acciones, también puede especificar marcas de configuración.

Puedes usar la --high_priority_workers marca para especificar un nombre mnemotécnico que se debe ejecutar en lugar de la prioridad normal mnemotécnicos. Esto puede ayudar a priorizar las acciones que siempre están en la ruta crítica. Si hay dos o más trabajadores de alta prioridad ejecutando solicitudes, que otros trabajadores no puedan ejecutarse. Esta marca se puede usar varias veces.

Pasar la marca --worker_sandboxing hace que cada solicitud de trabajador use un directorio de zona de pruebas independiente para todas sus entradas. La configuración de la zona de pruebas requiere un tiempo adicional, especialmente en macOS, pero ofrece una mejor garantía de exactitud.

La marca --worker_quit_after_build es útil principalmente para la depuración y la generación de perfiles. Esta marca fuerza a todos los trabajadores se cierre cuando finalice la compilación. También puedes pasar --worker_verbose a obtener más resultados sobre lo que hacen los trabajadores. Esta marca se refleja en el verbosity en WorkRequest, lo que permite que también se puedan sean más detallados.

Los trabajadores almacenan sus registros en el directorio <outputBase>/bazel-workers, por ejemplo /tmp/_bazel_larsrc/191013354bebe14fdddae77f2679c3ef/bazel-workers/worker-1-Javac.log El nombre del archivo incluye el ID del trabajador y la mnemotecnia. Como puede haber más de un WorkerKey por nombre mnemotécnico, es posible que veas más de worker_max_instances archivos de registro para un nombre mnemotécnico determinado.

En el caso de las compilaciones de Android, consulta los detalles en la página Rendimiento de compilaciones de Android.

Implementa trabajadores persistentes

Consulta la página sobre cómo crear trabajadores persistentes para obtener más información. información sobre cómo convertir a un trabajador.

En este ejemplo, se muestra una configuración de Starlark para un trabajador que usa JSON:

args_file = ctx.actions.declare_file(ctx.label.name + "_args_file")
ctx.actions.write(
    output = args_file,
    content = "\n".join(["-g", "-source", "1.5"] + ctx.files.srcs),
)
ctx.actions.run(
    mnemonic = "SomeCompiler",
    executable = "bin/some_compiler_wrapper",
    inputs = inputs,
    outputs = outputs,
    arguments = [ "-max_mem=4G",  "@%s" % args_file.path],
    execution_requirements = {
        "supports-workers" : "1", "requires-worker-protocol" : "json" }
)

Con esta definición, el primer uso de esta acción comenzaría con la ejecución de la línea de comandos /bin/some_compiler -max_mem=4G --persistent_worker. Una solicitud para compilar Foo.java, se vería de la siguiente manera:

NOTA: Si bien la especificación del búfer de protocolo usa “mayúsculas y minúsculas” (request_id), el protocolo JSON usa “mayúsculas y minúsculas” (requestId). En este documento, usaremos mayúsculas y minúsculas en los ejemplos de JSON, pero mayúsculas y minúsculas cuando hablemos del campo, independientemente del protocolo.

{
  "arguments": [ "-g", "-source", "1.5", "Foo.java" ]
  "inputs": [
    { "path": "symlinkfarm/input1", "digest": "d49a..." },
    { "path": "symlinkfarm/input2", "digest": "093d..." },
  ],
}

El trabajador recibe esto en stdin en formato JSON delimitado por saltos de línea (porque requires-worker-protocol está configurado como JSON). A continuación, el trabajador realiza la acción, y envía un WorkResponse con formato JSON a Bazel en su stdout. Luego, Bazel analiza esta respuesta y la convierte de forma manual en un proto WorkResponse. Para comunicarse con el trabajador asociado usando protobuf con codificación binaria en lugar de JSON, requires-worker-protocol se configuraría como proto, de la siguiente manera:

  execution_requirements = {
    "supports-workers" : "1" ,
    "requires-worker-protocol" : "proto"
  }

Si no incluyes requires-worker-protocol en los requisitos de ejecución, Bazel usará de forma predeterminada la comunicación del trabajador para usar protobuf.

Bazel deriva el WorkerKey del mnemotécnico y de las marcas compartidas, por lo que si este configuración permitía cambiar el parámetro max_mem, un trabajador independiente para cada valor usado. Esto puede provocar un consumo excesivo de memoria si se usan demasiadas variaciones.

Actualmente, cada trabajador solo puede procesar una solicitud a la vez. El experimento Trabajadores multiplex permite usar varios subprocesos, si la herramienta subyacente tiene varios subprocesos y el wrapper está configurado para entender esto.

En este repositorio de GitHub, puedes ver ejemplos de wrappers de trabajadores escritos en Java y en Python. Si funcionan en JavaScript o TypeScript, el Paquete@bazel/worker y Ejemplo de nodejs worker podría serte útil.

¿Cómo afectan los trabajadores a la zona de pruebas?

Si usas la estrategia worker de forma predeterminada, no se ejecutará la acción en una simulación, similar a la estrategia local. Puedes configurar la marca --worker_sandboxing para ejecutar todos los trabajadores dentro de las zonas de pruebas y asegurarte de que cada ejecución de la herramienta solo vea los archivos de entrada que debería tener. La herramienta aún podrían filtrar información entre solicitudes internamente, por ejemplo, a través de un la caché. Usando la estrategia dynamic requiere que los trabajadores estén en una zona de pruebas.

Para permitir el uso correcto de las cachés del compilador con los trabajadores, se pasa un resumen con cada archivo de entrada. Por lo tanto, el compilador o el wrapper pueden verificar si la entrada sigue siendo válida sin tener que leer el archivo.

Incluso cuando se usan los resúmenes de entrada para proteger contra el almacenamiento en caché no deseado, los trabajadores en zona de pruebas ofrecen una zona de pruebas menos estricta que una zona de pruebas pura, ya que la herramienta puede conservar otro estado interno que se vio afectado por solicitudes anteriores.

Los trabajadores de multiplexación solo se pueden poner en la zona de pruebas si la implementación del trabajador lo admite, y esta zona de pruebas se debe habilitar por separado con la marca --experimental_worker_multiplex_sandboxing. Obtén más detalles en el documento de diseño).

Lecturas adicionales

Para obtener más información sobre los trabajadores persistentes, consulta: