Ninja: Opción para usar características de archivo en lugar de marcas de tiempo

Creado en 14 ago. 2018  ·  15Comentarios  ·  Fuente: ninja-build/ninja

Como se señaló en otros números, el uso de marcas de tiempo para determinar si se debe reconstruir algo puede ser problemático. Si bien las marcas de tiempo son convenientes y relativamente rápidas, a menudo es deseable generar decisiones clave sobre alguna característica intrínseca del archivo en sí, como un hash de su contenido.

Uso mucho git y es molesto que el simple hecho de cambiar las ramas desencadene reconstrucciones. Idealmente, podría cambiar de mi rama de trabajo actual a otra rama, no tocar ningún archivo y luego volver a la rama original y no tener que reconstruir nada en absoluto. Hasta donde yo sé, eso no es posible si el sistema de compilación usa marcas de tiempo. El uso de hashes de archivos resolvería este problema en particular.

Entiendo que el uso de hashes de archivos u otras características intrínsecas de los archivos podrían ralentizar a Ninja, por lo que usarlos debería ser una opción.

feature

Comentario más útil

También he usado hashing en el trabajo, con gran éxito. Está basado en el #929, pero con un montón de parches, como se puede ver en https://github.com/moroten/ninja/commits/hashed. hash_input = 1 en reglas seleccionadas es muy conveniente. Mi rama todavía contiene un error en el que los archivos son stat con demasiada frecuencia, O(n^2) en lugar de O(n) . El error está relacionado con los bordes falsos.

Un problema es cómo manejar los bordes falsos. Uso bordes falsos para agrupar, por ejemplo, archivos de encabezado. Por lo tanto, mi implementación itera recursivamente a través de bordes falsos. También se relaciona con el error en #1021.

Otra idea es hacer que el hash sea miembro de primera clase de ninja, es decir, mover los hash al registro de compilación. Usar SHA256 sería un paso para agregar soporte para la API de ejecución remota de Bazel. Se puede encontrar una implementación de C++ en https://gitlab.com/bloomberg/recc/. ¿No sería eso bastante agradable?

Desafortunadamente, clasificar la semántica de los bordes falsos probablemente romperá la compatibilidad con versiones anteriores.

Todos 15 comentarios

929 tiene una implementación. Aunque se usa con éxito (en una bifurcación) para miles de compilaciones diarias, no se consideró para la fusión.

929 es de subproceso único y, por lo tanto, puede ser más lento como ccache u otras soluciones. También creo que esto debería ser un indicador de línea de comando en su lugar, para que no requiera cambios en las definiciones de compilación.

No puede (o al menos no debería) ser un indicador de línea de comando, ya que entonces el hashing se aplicaría a todas las reglas. Hashing, por ejemplo, todas las entradas de las reglas de enlace es costoso y, por lo tanto, no es deseable, mientras que los archivos fuente y sus dependencias conocidas son buenos candidatos. Para esa distinción, tiene que ser parte de la descripción de la compilación. Además, para hacer uso de la función, debe usar la bandera constantemente todo el tiempo. No solo a veces.

Respondiendo al argumento de subproceso único: Sí, agrega instrucciones al bucle de subproceso único. En realidad, eso solo importa si el subproceso único obtiene más trabajo del que puede trabajar (es decir, terminan más reglas de las que el subproceso puede procesar (depslog+hashlog+...)). Solo entonces hash duele. De lo contrario, el bucle de subproceso único espera a que finalicen los trabajos de todos modos. Nunca vimos un ninja ocupado con hash incluso con experimentos -j1000. (Y para las reglas de finalización rápida, el hashing de todos modos no es interesante para ahorrar tiempo).

Considere también: el hash con el hash de murmullo es considerablemente rápido e incluso los archivos fuente grandes tardan solo unos milisegundos en ser hash. Además, el hashing ocurre justo después de que el compilador haya visto el archivo fuente (y las dependencias). Por lo tanto, generalmente se leen desde la memoria caché del sistema de archivos.
Dado que el hashing ocurre durante la compilación (en paralelo a las reglas ejecutadas), el tiempo de compilación general generalmente no se ve afectado de manera medible.

Por último, la implementación en el n.° 929 es opcional y no tiene costo para las personas que no usan la función (además de la declaración if).

Hashing, por ejemplo, todas las entradas de las reglas de enlace es costoso y, por lo tanto, no es deseable, mientras que los archivos fuente y sus dependencias conocidas son buenos candidatos.

Diría que el hash de las entradas al enlazador es especialmente deseable, ya que a menudo puede resultar en que el enlace se omita por completo (por ejemplo, para cambios de formato o cuando se cambian los comentarios). A medida que la compilación de los archivos de objetos finaliza pieza por pieza, el cálculo del hash puede realizarse mientras se ejecuta la compilación (como señaló).

Si es realmente demasiado lento (por ejemplo, con grandes bibliotecas estáticas), podríamos pensar en implementar hashes solo para entradas puras y no para archivos intermedios. Eso resolvería al menos el caso de "cambiar las ramas de Git provoca reconstrucciones completas".

Además, para hacer uso de la función, debe usar la bandera constantemente todo el tiempo. No solo a veces.

Diría que es una ventaja: si estoy trabajando en una sola rama y quiero iterar rápido, no usaría hashes. Si estoy comparando diferentes ramas de características, usaría hashes.

Además, para hacer uso de la función, debe usar la bandera constantemente todo el tiempo. No solo a veces.

Diría que es una ventaja: si estoy trabajando en una sola rama y quiero iterar rápido, no usaría hashes. Si estoy comparando diferentes ramas de características, usaría hashes.

Pero luego tendría que haber una manera de cambiar de no-hashing a hashing, lo que significa que el estado actual de los archivos debería ser hash, por lo que la próxima reconstrucción puede usar los hash (que probablemente no existían o están fuera de fecha si no pasaste la bandera).

Supongo que el hashing todavía usa la marca de tiempo primero, de modo que si las marcas de tiempo coinciden, no hay necesidad de comparar los hash. Significaría que las primeras compilaciones podrían volver a compilar innecesariamente algunos archivos, pero eso no debería suceder con frecuencia (después de todo, la mayoría de las veces, la heurística de marca de tiempo es correcta).

También he usado hashing en el trabajo, con gran éxito. Está basado en el #929, pero con un montón de parches, como se puede ver en https://github.com/moroten/ninja/commits/hashed. hash_input = 1 en reglas seleccionadas es muy conveniente. Mi rama todavía contiene un error en el que los archivos son stat con demasiada frecuencia, O(n^2) en lugar de O(n) . El error está relacionado con los bordes falsos.

Un problema es cómo manejar los bordes falsos. Uso bordes falsos para agrupar, por ejemplo, archivos de encabezado. Por lo tanto, mi implementación itera recursivamente a través de bordes falsos. También se relaciona con el error en #1021.

Otra idea es hacer que el hash sea miembro de primera clase de ninja, es decir, mover los hash al registro de compilación. Usar SHA256 sería un paso para agregar soporte para la API de ejecución remota de Bazel. Se puede encontrar una implementación de C++ en https://gitlab.com/bloomberg/recc/. ¿No sería eso bastante agradable?

Desafortunadamente, clasificar la semántica de los bordes falsos probablemente romperá la compatibilidad con versiones anteriores.

Mientras tanto, inventé una solución para mi caso de uso de cambiar de rama en Chromium (que es particularmente doloroso); el pequeño programa Go y el script que uso están aquí: https://github.com/bromite/mtool

Siéntase libre de adaptarlo a sus casos de uso, si funciona para Chromium, apuesto a que también funcionará para proyectos de construcción más pequeños (y toma un tiempo insignificante para mis ejecuciones). El único inconveniente es que si usa el script que publiqué allí tal como está, llenará de archivos .mtool en cada directorio principal del repositorio de git, pero nada que un gitignore global no pueda curar.

Es interesante notar que utilizo la salida git ls-files --stage para las necesidades de hashing; es posible (pero menos eficiente) pedirle a git que haga hash también de archivos no indexados si su compilación depende de ellos.

En cuanto a las funciones, uno esperaría que para implementar la función que se analiza aquí, ninja pudiera hacer lo mismo internamente (sin depender de git) y con resultados de rendimiento similares.

Después de una mirada rápida, esos parches ninja no parecen codificar la línea de comandos del compilador además de los archivos de entrada. ¿Me estoy perdiendo algo?

La línea de comando ya está codificada por ninja y almacenada en el registro de compilación.

Supongo que el hashing todavía usa la marca de tiempo primero, de modo que si las marcas de tiempo coinciden, no hay necesidad de comparar los hash. Significaría que las primeras compilaciones podrían volver a compilar innecesariamente algunos archivos, pero eso no debería suceder con frecuencia (después de todo, la mayoría de las veces, la heurística de marca de tiempo es correcta).

Eso estaría muy mal. La comparación de hashes se puede elidir si las marcas de tiempo no coinciden; el sistema de compilación puede suponer que la dependencia se modificó y, si no se modificó realmente (solo se tocó), la compilación será subóptima pero correcta. Sin embargo, si las marcas de tiempo coinciden, aún es posible que la dependencia se haya modificado y su marca de tiempo se haya restablecido a la fuerza (EDITAR: o que el objetivo se haya tocado y, por lo tanto, se haya hecho más nuevo que sus dependencias). Sin una doble verificación comparando hashes, esto daría como resultado una compilación incorrecta.

Supongo que Ninja ya asume y se salta todo el trabajo cuando las marcas de tiempo coinciden. Entonces, en su ejemplo, esta sería una compilación incorrecta de todos modos.

No tengo conocimiento (probablemente debido a mi ignorancia) de ninguna herramienta que produzca una salida diferente y mantenga la marca de tiempo anterior. ¿Por qué uno haría eso?

En mi humilde opinión, omitir la verificación de hash cuando las marcas de tiempo coinciden es una optimización muy válida.

La verificación de hash simplemente evitaría algunas reconstrucciones "falsas y sucias", manteniendo la semántica existente ya proporcionada por Ninja.

El problema no son las reconstrucciones falsas y sucias, sino las no reconstrucciones falsas y limpias. Un git checkout toca todo lo que sobrescribe. Puede hacer que un objetivo sea más nuevo que una dependencia (sí, las personas confirman el código generado por varias razones válidas). Una verificación de hash evitaría una no reconstrucción de limpieza falsa en este caso.

@rulatir Creo que en su mayoría entiendo lo que estás diciendo :) Supongo que esa es una de las razones por las que Bazel y otros sistemas de compilación basados ​​​​en verificaciones de hash están realmente en contra de los resultados de destino en el árbol.

Aunque, ¿no se resolvería este problema si el sistema de compilación verificara si los objetivos son más nuevos que el tiempo conocido anteriormente y los reconstruiría si fuera necesario?

@rulatir Creo que en su mayoría entiendo lo que estás diciendo :) Supongo que esa es una de las razones por las que Bazel y otros sistemas de compilación basados ​​​​en verificaciones de hash están realmente en contra de los resultados de destino en el árbol.

¿Cómo depende el hash de dónde está el archivo?

Aunque, ¿no se resolvería este problema si el sistema de compilación verificara si los objetivos son más nuevos que el tiempo conocido anteriormente y los reconstruiría si fuera necesario?

Entiendo que el principal beneficio de usar marcas de tiempo es evitar la necesidad de mantener una base de datos separada que realice un seguimiento de las firmas de versiones "anteriormente conocidas". Si está dispuesto a renunciar a ese beneficio, ¿por qué esas firmas no deberían ser hashes?

¿Fue útil esta página
0 / 5 - 0 calificaciones