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) o es
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 el
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 permite 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 múltiples solicitudes a un servidor el proceso de administración de recursos.
Los trabajadores persistentes se implementan para varios lenguajes, incluido Java, Scala Kotlin y mucho más.
Los programas que usan un entorno de ejecución NodeJS pueden usar el @bazel/worker para implementar el protocolo del trabajador.
Usa trabajadores persistentes
Bazel 0.27 y versiones posteriores
usa trabajadores persistentes de forma predeterminada cuando ejecuta compilaciones, aunque
ejecución tiene prioridad. Para acciones que no admiten trabajadores persistentes,
Bazel recurre a iniciar una instancia de herramienta para cada acción. Puedes especificar
configura tu compilación para que use trabajadores persistentes mediante la configuración de worker
estrategia para la herramienta aplicable
mnemotécnicos. 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. Para Java, las compilaciones pueden ser de 2 a 4 veces más rápido y 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 el modo
de palabras clave, pasa el
--experimental_spawn_scheduler
marca. 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 el número máximo de instancias de trabajador por
conjunto mnemotécnico y de marcas (ver a continuación), por lo que en un sistema mixto podrías terminar usando
mucha 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.
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 en un 14% de mejora 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 se beneficia aún más. Las compilaciones limpias son relativamente raro, pero cambiar un solo archivo entre compilaciones es común, en en especial en el desarrollo basado en pruebas. El ejemplo anterior también tiene algunas APIs acciones de empaquetado que pueden reemplazar 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):
Figura 2: Gráfico de mejoras en el rendimiento de las compilaciones incrementales.
La aceleración depende del cambio que se realice. Una aceleración de un factor 6 es medida en la situación anterior, cuando se cambia una constante de uso común.
Modifica trabajadores persistentes
Puedes pasar el
--worker_extra_flag
para especificar marcas de inicio a los trabajadores, con clave mnemotécnica. Por ejemplo:
que pasa --worker_extra_flag=javac=--debug
activa la depuración solo para Javac.
Solo se puede configurar una marca de trabajador por uso de esta marca, y solo para un nombre nemotécnico.
Los trabajadores no solo se crean por separado para cada mnemotécnico, sino también para
variaciones en sus marcas de inicio. Cada combinación de mnemónico y inicio
se combinan en un WorkerKey
, y para cada WorkerKey
hasta un
Se pueden crear 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
ruta de acceso. 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.
Si pasas el
--worker_sandboxing
hace que cada solicitud de trabajador use un directorio de zona de pruebas independiente para todas sus
de datos. Configurar la zona de pruebas lleva más tiempo.
especialmente en macOS, pero ofrece una garantía de precisión mayor.
El
--worker_quit_after_build
es útil sobre todo para depurar y generar 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 el nombre nemotécnico. 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.
Para compilaciones de Android, consulta los detalles en la Página de rendimiento de compilación de Android
Cómo implementar 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
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 "snake case" (request_id
),
el protocolo JSON usa “capitalización medial” (requestId
) En este documento, usaremos
mayúsculas mediales en los ejemplos de JSON, pero Snake case cuando se habla 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
se configura como JSON). A continuación, el trabajador realiza la acción,
y envía un WorkResponse
con formato JSON a Bazel en su stdout. Bazel y luego
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 generar un consumo de memoria excesivo si
se usan demasiadas variaciones.
Por el momento, 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 repo de GitHub, Puedes ver ejemplos de wrappers de trabajador 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?
El uso de la estrategia worker
de forma predeterminada no ejecuta la acción en una
zona de pruebas, similar a la estrategia local
. Puedes configurar
La marca --worker_sandboxing
para ejecutar todos los trabajadores dentro de las zonas de pruebas y garantizar que cada uno
ejecución de la herramienta solo ve 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 trabajadores, se pasa un resumen. con cada archivo de entrada. Por lo tanto, el compilador o el wrapper pueden comprobar si la entrada siguen siendo válidos sin tener que leer el archivo.
Incluso cuando se usan los resúmenes de entrada para protegerse contra el almacenamiento en caché los trabajadores ofrecen un entorno aislado menos estricto que uno puro, ya que la herramienta puede mantener otro estado interno que se vio afectado por solicitudes anteriores.
Los trabajadores multiplex solo pueden estar en zona de pruebas si la implementación del trabajador lo admite.
y esta zona de pruebas debe habilitarse por separado con el
--experimental_worker_multiplex_sandboxing
. Ver más detalles en
el documento de diseño).
Lecturas adicionales
Para obtener más información sobre los trabajadores persistentes, consulta:
- Entrada de blog original sobre los trabajadores persistentes
- Descripción de la implementación de Haskell {: .external}
- Entrada de blog de Mike Morearty {: .external}
- Desarrollo de frontend con Bazel: Angular/TypeScript y trabajadores persistentes con Asana {: .external}
- Explicación de las estrategias de Bazel {: .external}
- Debate informativo sobre la estrategia del trabajador en la lista de distribución de bazel-debate {: .external}