El modelo de incrementalidad y evaluación paralelo de Bazel.
Modelo de datos
El modelo de datos consta de los siguientes elementos:
SkyValue
También se denominan nodos.SkyValues
son objetos inmutables que contienen todos los datos compilados en el transcurso de la compilación y las entradas de la compilación. Por ejemplo: archivos de entrada, archivos de salida, destinos y archivos objetivos.SkyKey
Un nombre inmutable corto para hacer referencia a unSkyValue
, por ejemplo,FILECONTENTS:/tmp/foo
oPACKAGE://foo
.SkyFunction
Compila nodos en función de sus claves y nodos dependientes.- Grafo de nodo Es una estructura de datos que contiene la relación de dependencia entre nodos.
Skyframe
El nombre interno del marco de trabajo de evaluación incremental es Bazel es en función de ellos.
Evaluación
Para lograr una compilación, se evalúa el nodo que representa la solicitud de compilación.
Primero, Bazel encuentra el SkyFunction
correspondiente a la clave del nivel superior.
SkyKey
Luego, la función solicita la evaluación de los nodos que necesita
evaluar el nodo de nivel superior, lo que, a su vez, generará otras llamadas a SkyFunction
hasta que se alcanzan los nodos hoja. Los nodos de hoja suelen ser los que representan
archivos de entrada en el sistema de archivos. Por último, Bazel termina con el valor de la
SkyValue
de nivel superior, algunos efectos secundarios (como archivos de salida en el archivo
sistema) y un grafo acíclico dirigido de las dependencias entre los nodos
involucradas en la compilación.
Un SkyFunction
puede solicitar SkyKeys
en varios pases si no se sabe en
todos los nodos que necesita para hacer su trabajo. Un ejemplo sencillo es evaluar
un nodo de archivo de entrada que resulta ser un symlink: la función intenta leer
el archivo, se da cuenta de que es un symlink y, por lo tanto, recupera el nodo del sistema de archivos
que representan el destino del symlink. Pero eso en sí puede ser un symlink, en
en cuyo caso la función original
también deberá recuperar su objetivo.
Las funciones se representan en el código con la interfaz SkyFunction
y la
los servicios que le proporciona una interfaz llamada SkyFunction.Environment
. Estos
lo que las funciones pueden hacer:
- Solicita la evaluación de otro nodo llamando a
env.getValue
. Si el nodo está disponible, se muestra su valor; de lo contrario, se muestranull
y se espera que la función en sí muestrenull
. En el último caso, el nodo dependiente y, luego, el compilador de nodos original Se vuelve a invocar, pero esta vez la misma llamada aenv.getValue
mostrará un un valor que no esnull
. - Llama a
env.getValues()
para solicitar la evaluación de muchos otros nodos. Esto hace básicamente lo mismo, con la excepción de que los nodos dependientes que se evalúen en paralelo. - Hacer cálculos durante la invocación
- Tener efectos secundarios, por ejemplo, escribir archivos en el sistema Necesidades de cuidado que dos funciones diferentes evitan pisarse la una de la otra en los dedos de los pies. En general, escribir efectos secundarios (en los que los datos fluyen hacia afuera desde Bazel) los efectos secundarios (donde los datos fluyen en Bazel sin una dependencia registrada) no lo son, porque son una dependencia no registrada lo que puede generar compilaciones incrementales incorrectas.
Las implementaciones de SkyFunction
con un buen comportamiento evitan el acceso a los datos de cualquier otra manera.
que solicitar dependencias (por ejemplo, leer directamente el sistema de archivos),
ya que eso hace que Bazel no registre la dependencia de datos en el archivo.
que se leyó, lo que da como resultado compilaciones incrementales incorrectas.
Una vez que una función tenga suficientes datos para realizar su trabajo, debería mostrar un error que no sea null
.
valor que indica la finalización.
Esta estrategia de evaluación tiene una serie de beneficios:
- Hermeticidad. Si las funciones solo solicitan datos de entrada dependiendo de otros nodos, Bazel puede garantizar que, si el estado de entrada es el mismo, se devuelven los mismos datos. Si todas las funciones de cielo son determinísticas, que toda la compilación también será determinista.
- Incrementalidad correcta y perfecta. Si todos los datos de entrada de todas las funciones se registra, Bazel puede invalidar solo el conjunto exacto de nodos que necesitan invalide cuando cambien los datos de entrada.
- Paralelismo. Dado que las funciones solo pueden interactuar entre sí mediante solicitar dependencias, las funciones que no dependen unas de otras pueden ejecutar en paralelo, y Bazel puede garantizar que el resultado sea el mismo que se ejecutaron de forma secuencial.
Incrementalidad
Debido a que las funciones solo pueden acceder a los datos de entrada dependiendo de otros nodos, Bazel puede crear un grafo de flujo de datos completo desde los archivos de entrada hasta la salida y usar esta información solo para reconstruir los nodos que realmente necesitan que debe reconstruirse: el cierre transitivo inverso del conjunto de archivos de entrada modificados.
En particular, existen dos estrategias de incrementalidad posibles: la ascendente y el de arriba hacia abajo. ¿Cuál es la óptima depende de cómo el gráfico de dependencia cómo es el aspecto.
Durante la invalidación ascendente, después de que se crea un grafo y el conjunto de valores se conocen entradas, se invalidan todos los nodos que dependen transitivamente archivos modificados. Esto es óptimo si se compilará el mismo nodo de nivel superior de nuevo. Ten en cuenta que la invalidación ascendente requiere que se ejecute
stat()
en todos archivos de entrada de la compilación anterior para determinar si se modificaron. Esta Se puede mejorar usandoinotify
o un mecanismo similar para obtener información sobre archivos modificados.Durante la invalidación descendente, el cierre transitivo del nodo de nivel superior y solo se mantienen los nodos cuyo cierre transitivo está limpio. Esto es mejor si el gráfico de nodos es grande, pero la próxima compilación solo necesita un pequeño subconjunto: la invalidación ascendente invalidaría el gráfico más grande. de la primera compilación, a diferencia de la invalidación descendente, que solo recorre la parte gráfico de la segunda compilación.
Bazel solo realiza invalidaciones ascendentes.
Para obtener una mayor incrementalidad, Bazel usa la reducción de cambios: si un nodo se se invalida, pero luego de reconstruir, se descubre que su nuevo valor es el mismo como su valor anterior, los nodos que se invalidaron debido a un cambio en este nodo son “resucitados”.
Esto resulta útil, por ejemplo, si se modifica un comentario en un archivo C++, el código
.o
generado a partir de él será el mismo, por lo que no es necesario llamar
el vinculador de nuevo.
Vinculación / compilación incremental
La principal limitación de este modelo es que la invalidación de un nodo es asunto de todo o nada: cuando una dependencia cambia, el nodo dependiente siempre está reconstruir desde cero, incluso si existiera un algoritmo mejor que mutaría el valor anterior del nodo según los cambios. Algunos ejemplos en los que esto serán útiles:
- Vinculación incremental
- Cuando un solo archivo de clase cambia en un archivo JAR, es posible modificar el archivo JAR en su lugar, en lugar de compilarlo desde cero nuevamente.
El motivo por el que Bazel no admite estas cosas de manera fundamentada. tiene dos aspectos:
- El rendimiento fue limitado.
- Dificultad para validar que el resultado de la mutación es el mismo que el una recompilación limpia, y Google valora las compilaciones que son repetible.
Hasta ahora, era posible lograr un rendimiento suficientemente bueno al descomponer un un paso de compilación costoso y lograr una reevaluación parcial de esa manera. Por ejemplo: En una app para Android, puedes dividir todas las clases en varios grupos y DEX por separado. De esta forma, si no se modifican las clases de un grupo, la conversión a DEX que no es necesario rehacer.
Asigna a conceptos de Bazel
Este es un resumen de alto nivel de los elementos SkyFunction
y SkyValue
clave.
implementaciones que Bazel usa para realizar una compilación:
- FileStateValue Es el resultado de un
lstat()
. En el caso de los archivos existentes, también procesa información adicional para detectar cambios en el archivo. Este es el nodo de nivel más bajo en el gráfico de Skyframe y no tiene dependencias. - FileValue predeterminado Se usa con cualquier elemento que se preocupe por el contenido real o
resuelta de un archivo. Depende del
FileStateValue
y cualquier symlink que deba resolverse (comoFileValue
paraa/b
necesita la ruta de acceso resuelta dea
y la ruta de acceso resuelta dea/b
). El la distinción entreFileValue
yFileStateValue
es importante porque este último puede usarse en los casos en que el contenido del archivo no esté realmente necesarias. Por ejemplo, el contenido del archivo es irrelevante cuando Evaluación de los globs del sistema de archivos (comosrcs=glob(["*/*.java"])
) - DirectoryListingStateValue. El resultado de
readdir()
. Me gustaFileStateValue
: Es el nodo de nivel más bajo y no tiene dependencias. - DirectoryListingValue. Se usa por todo lo que se preocupa por las entradas de
un directorio. Depende del
DirectoryListingStateValue
correspondiente, como así como elFileValue
asociado del directorio. - PackageValue: Representa la versión analizada de un archivo BUILD. Depende de
el
FileValue
del archivoBUILD
asociado y también de forma transitiva en cualquierDirectoryListingValue
que se usa para resolver los globs del paquete (la estructura de datos que representa el contenido de un archivoBUILD
de forma interna). - ConfiguredTargetValue. Representa un destino configurado, que es una tupla
del conjunto de acciones generadas durante el análisis de un objetivo y
información proporcionada a destinos configurados dependientes. Depende del
PackageValue
en el que se encuentra el destino correspondiente, elConfiguredTargetValues
de dependencias directas y un nodo especial que representa la compilación configuración. - ArtifactValue asociado. Representa un archivo en la compilación, ya sea una fuente o un
de salida. Los artefactos son casi equivalentes a los archivos y se usan para
hacer referencia a los archivos durante la ejecución real de los pasos de compilación. Archivos de origen
Depende del
FileValue
del nodo asociado y de los artefactos de salida dependerá del objetoActionExecutionValue
de cualquier acción que genere la artefacto. - ActionExecutionValue predeterminado. Representa la ejecución de una acción. Depende de
el
ArtifactValues
de sus archivos de entrada. La acción que ejecuta está contenida dentro de su SkyKey, lo que es contrario al concepto de que se debe pequeño. Ten en cuenta queActionExecutionValue
yArtifactValue
no se usan si la fase de ejecución no se ejecuta.
Como ayuda visual, este diagrama muestra las relaciones entre Implementaciones de SkyFunction después de una compilación de Bazel: