Ninja: Option zur Verwendung von Dateimerkmalen anstelle von Zeitstempeln

Erstellt am 14. Aug. 2018  ·  15Kommentare  ·  Quelle: ninja-build/ninja

Wie in anderen Problemen erwähnt, kann die Verwendung von Zeitstempeln zur Bestimmung, ob etwas neu erstellt werden soll, problematisch sein. Während Zeitstempel bequem und relativ schnell sind, ist es oft wünschenswert, Schlüsselerstellungsentscheidungen auf einigen Eigenschaften zu treffen, die der Datei selbst innewohnen, wie z. B. ein Hash ihres Inhalts.

Ich benutze Git viel und es ist ärgerlich, dass das einfache Ändern von Branches Rebuilds auslöst. Im Idealfall wäre ich in der Lage, von meinem aktuellen Arbeitszweig zu einem anderen Zweig zu wechseln, keine Dateien anzufassen und dann zurück zum ursprünglichen Zweig zu wechseln, ohne überhaupt etwas neu erstellen zu müssen. Soweit ich weiß, ist das nicht möglich, wenn das Build-System Zeitstempel verwendet. Die Verwendung von Datei-Hashes würde dieses spezielle Problem lösen.

Ich verstehe, dass die Verwendung von Datei-Hashes oder anderen solchen intrinsischen Dateieigenschaften Ninja verlangsamen könnte, daher sollte ihre Verwendung eine Option sein.

feature

Hilfreichster Kommentar

Ich habe Hashing auch bei der Arbeit verwendet, mit großem Erfolg. Es basiert auf #929, aber mit einer Reihe von Patches, wie in https://github.com/moroten/ninja/commits/hashed zu sehen ist. hash_input = 1 für ausgewählte Regeln ist sehr praktisch. Mein Zweig enthält immer noch einen Fehler, bei dem Dateien zu oft stat sind, O(n^2) statt O(n) . Der Fehler hängt mit falschen Kanten zusammen.

Ein Problem besteht darin, wie mit falschen Rändern umzugehen ist. Ich verwende falsche Kanten, um zB Header-Dateien zu gruppieren. Daher iteriert meine Implementierung rekursiv durch falsche Kanten. Es bezieht sich auch auf den Fehler in #1021.

Ein weiterer Gedanke ist, das Hashing erstklassig zu Ninja zu machen, dh die Hashes in das Build-Log zu verschieben. Die Verwendung von SHA256 wäre ein Schritt, um Unterstützung für die Remote-Ausführungs-API von Bazel hinzuzufügen. Eine C++-Implementierung finden Sie unter https://gitlab.com/bloomberg/recc/. Wäre das nicht schön?

Leider wird das Aussortieren der Semantik für falsche Kanten wahrscheinlich die Abwärtskompatibilität beeinträchtigen.

Alle 15 Kommentare

929 hat eine Implementierung. Obwohl es (in einem Fork) täglich für Tausende von Builds erfolgreich verwendet wird, wurde es nicht für eine Zusammenführung in Betracht gezogen.

929 ist Single-Threaded und kann daher langsamer sein wie ccache oder andere Lösungen. Ich denke auch, dass dies stattdessen ein Befehlszeilen-Flag sein sollte, damit keine Änderungen an den Build-Definitionen erforderlich sind.

Es kann (oder sollte zumindest nicht) ein Befehlszeilen-Flag sein, da dann Hashing für alle Regeln gelten würde. Das Hashen zB aller Eingaben von Verknüpfungsregeln ist teuer und daher nicht erwünscht, während Quelldateien und ihre bekannten Abhängigkeiten gute Kandidaten sind. Für diese Unterscheidung muss es Teil der Build-Beschreibung sein. Um die Funktion nutzen zu können, sollten Sie das Flag außerdem immer konsequent verwenden. Nicht nur manchmal.

Antwort auf das Single-Threaded-Argument: Ja, es fügt der Single-Thread-Schleife Anweisungen hinzu. In Wirklichkeit spielt das nur eine Rolle, wenn der einzelne Thread mehr Arbeit bekommt, als er abarbeiten kann (dh mehr Regeln beenden, als der eine Thread verarbeiten kann (depslog+hashlog+...)). Nur dann tut Hashing weh. Andernfalls wartet die Single-Thread-Schleife sowieso darauf, dass Jobs beendet werden. Wir haben selbst bei -j1000-Experimenten nie einen beschäftigten Ninja mit Hashing gesehen. (Und für Fast-Finishing-Regeln ist Hashing sowieso nicht interessant, um Zeit zu sparen.)

Bedenken Sie auch: Das Hashing mit murmur hash ist ziemlich schnell und selbst große Quelldateien brauchen nur einige Millisekunden, um gehasht zu werden. Darüber hinaus erfolgt das Hashing unmittelbar nachdem die Quelldatei (und die Abhängigkeiten) vom Compiler gesehen wurden. Daher werden sie normalerweise aus dem Dateisystem-Cache gelesen.
Da Hashing während des Builds stattfindet (parallel zu ausgeführten Regeln), wird die Gesamtbuildzeit normalerweise nicht messbar beeinflusst.

Schließlich ist die Implementierung in #929 optional und für Personen, die die Funktion nicht verwenden, kostenlos (abgesehen von der if-Anweisung).

Das Hashen zB aller Eingaben von Verknüpfungsregeln ist teuer und daher nicht erwünscht, während Quelldateien und ihre bekannten Abhängigkeiten gute Kandidaten sind.

Ich würde sagen, dass das Hashen von Eingaben in den Linker besonders erwünscht ist, da es oft dazu führen kann, dass die Verlinkung komplett übersprungen wird (zB bei Formatierungsänderungen oder wenn Kommentare geändert werden). Da die Kompilierung von Objektdateien Stück für Stück abgeschlossen wird, kann die Hash-Berechnung erfolgen, während der Build ausgeführt wird (wie Sie darauf hingewiesen haben).

Wenn es wirklich zu langsam ist (z. B. bei großen statischen Bibliotheken), könnten wir darüber nachdenken, Hashes nur für reine Eingaben und nicht für Zwischendateien zu implementieren. Das würde zumindest den Fall "Umschalten von Git-Zweigen verursacht vollständige Neuerstellungen" lösen.

Um die Funktion nutzen zu können, sollten Sie das Flag außerdem immer konsequent verwenden. Nicht nur manchmal.

Ich würde sagen, das ist ein Vorteil: Wenn ich an einem einzelnen Zweig arbeite und schnell iterieren möchte, würde ich keine Hashes verwenden. Wenn ich verschiedene Funktionszweige vergleiche, würde ich Hashes verwenden.

Um die Funktion nutzen zu können, sollten Sie das Flag außerdem immer konsequent verwenden. Nicht nur manchmal.

Ich würde sagen, das ist ein Vorteil: Wenn ich an einem einzelnen Zweig arbeite und schnell iterieren möchte, würde ich keine Hashes verwenden. Wenn ich verschiedene Funktionszweige vergleiche, würde ich Hashes verwenden.

Aber dann müsste es eine Möglichkeit geben, von Nicht-Hashing zu Hashing zu wechseln, was bedeutet, dass der aktuelle Status von Dateien gehasht werden müsste, damit der nächste Neuaufbau die Hashes verwenden kann (die wahrscheinlich nicht existierten oder nicht vorhanden sind). des Datums, wenn Sie die Flagge nicht passiert haben).

Ich würde vermuten, dass beim Hashing immer noch zuerst der Zeitstempel verwendet wird, sodass die Hashes nicht verglichen werden müssen, wenn die Zeitstempel übereinstimmen. Es würde bedeuten, dass die ersten paar Builds unnötigerweise einige Dateien neu kompilieren, aber das sollte nicht oft passieren (meistens ist die Timestamp-Heuristik doch richtig).

Ich habe Hashing auch bei der Arbeit verwendet, mit großem Erfolg. Es basiert auf #929, aber mit einer Reihe von Patches, wie in https://github.com/moroten/ninja/commits/hashed zu sehen ist. hash_input = 1 für ausgewählte Regeln ist sehr praktisch. Mein Zweig enthält immer noch einen Fehler, bei dem Dateien zu oft stat sind, O(n^2) statt O(n) . Der Fehler hängt mit falschen Kanten zusammen.

Ein Problem besteht darin, wie mit falschen Rändern umzugehen ist. Ich verwende falsche Kanten, um zB Header-Dateien zu gruppieren. Daher iteriert meine Implementierung rekursiv durch falsche Kanten. Es bezieht sich auch auf den Fehler in #1021.

Ein weiterer Gedanke ist, das Hashing erstklassig zu Ninja zu machen, dh die Hashes in das Build-Log zu verschieben. Die Verwendung von SHA256 wäre ein Schritt, um Unterstützung für die Remote-Ausführungs-API von Bazel hinzuzufügen. Eine C++-Implementierung finden Sie unter https://gitlab.com/bloomberg/recc/. Wäre das nicht schön?

Leider wird das Aussortieren der Semantik für falsche Kanten wahrscheinlich die Abwärtskompatibilität beeinträchtigen.

Inzwischen habe ich eine Lösung für meinen Anwendungsfall des Wechselns von Zweigen in Chromium (was besonders schmerzhaft ist) ausgeheckt; Das kleine Go-Programm und -Skript, das ich verwende, ist hier: https://github.com/bromite/mtool

Fühlen Sie sich frei, es an Ihre Anwendungsfälle anzupassen, wenn es für Chromium funktioniert, wette ich, dass es auch für kleinere Build-Projekte funktionieren wird (und es dauert vernachlässigbar lange für meine Läufe). Der einzige Nachteil ist, dass, wenn Sie das Skript, das ich dort veröffentlicht habe, so wie es ist, .mtool -Dateien in jedem übergeordneten Verzeichnis des Git-Repositorys herumstreunen, aber nichts, was ein globales gitignore heilen kann.

Interessant zu bemerken, dass ich die Ausgabe von git ls-files --stage für die Hashing-Anforderungen verwende; Es ist möglich (aber weniger effizient), git aufzufordern, auch nicht indizierte Dateien zu hashen, wenn Ihr Build von diesen abhängt.

In Bezug auf die Funktionen würde man erwarten, dass Ninja zur Implementierung der hier besprochenen Funktion dasselbe intern tun könnte (ohne sich auf Git zu verlassen) und mit ähnlichen Leistungsergebnissen.

Nach einem kurzen Blick scheinen diese Ninja-Patches die Compiler-Befehlszeile nicht zusätzlich zu den Eingabedateien zu hashen. Vermisse ich etwas?

Die Befehlszeile ist bereits von Ninja gehasht und im Build-Log gespeichert.

Ich würde vermuten, dass beim Hashing immer noch zuerst der Zeitstempel verwendet wird, sodass die Hashes nicht verglichen werden müssen, wenn die Zeitstempel übereinstimmen. Es würde bedeuten, dass die ersten paar Builds unnötigerweise einige Dateien neu kompilieren, aber das sollte nicht oft passieren (meistens ist die Timestamp-Heuristik doch richtig).

Das wäre sehr falsch. Der Vergleich von Hashes kann entfallen, wenn die Zeitstempel nicht übereinstimmen; Das Build-System kann davon ausgehen, dass die Abhängigkeit geändert wurde, und wenn sie nicht wirklich geändert (nur berührt) wurde, ist der Build suboptimal, aber korrekt. Wenn die Zeitstempel jedoch übereinstimmen, ist es immer noch möglich, dass die Abhängigkeit geändert und ihr Zeitstempel zwangsweise zurückgesetzt wurde (BEARBEITEN: oder dass das Ziel berührt und somit neuer gemacht wurde als seine Abhängigkeiten). Ohne doppelte Überprüfung durch Vergleichen von Hashes würde dies zu einem falschen Build führen.

Ich denke, Ninja nimmt bereits an und überspringt die ganze Arbeit, wenn die Zeitstempel übereinstimmen. In Ihrem Beispiel wäre dies also sowieso ein falscher Build.

Mir ist (wahrscheinlich aufgrund meiner Unkenntnis) kein Tool bekannt, das eine andere Ausgabe erzeugen und den vorherigen Zeitstempel beibehalten würde. Warum sollte man das tun?

IMHO ist das Überspringen der Hash-Prüfung, wenn die Zeitstempel übereinstimmen, eine sehr gültige Optimierung.

Die Hash-Prüfung würde einfach einige "false-dirty"-Neuaufbauten vermeiden, während die vorhandene Semantik, die bereits von Ninja bereitgestellt wird, beibehalten wird.

Das Problem sind nicht falsch-dirty Rebuilds, sondern falsch-saubere Nicht-Rebuilds. Ein Git-Checkout berührt alles, was es überschreibt. Es kann ein Ziel neuer als eine Abhängigkeit machen (ja, Leute schreiben generierten Code aus verschiedenen triftigen Gründen fest). Eine Hash-Prüfung würde in diesem Fall einen falsch-sauberen Nicht-Neuaufbau verhindern.

@rulatir Ich glaube, ich verstehe größtenteils, was du sagst :) Ich denke, das ist einer der Gründe, warum Bazel und andere Build-Systeme, die auf Hash-Checks basieren, wirklich gegen In-Tree-Zielausgaben sind.

Wäre dieses Problem nicht gelöst, wenn das Build-System prüfen würde, ob Ziele neuer als die bisher bekannte Zeit sind, und es gegebenenfalls neu erstellen würde?

@rulatir Ich glaube, ich verstehe größtenteils, was du sagst :) Ich denke, das ist einer der Gründe, warum Bazel und andere Build-Systeme, die auf Hash-Checks basieren, wirklich gegen In-Tree-Zielausgaben sind.

Wie hängt Hash davon ab, wo sich die Datei befindet?

Wäre dieses Problem nicht gelöst, wenn das Build-System prüfen würde, ob Ziele neuer als die bisher bekannte Zeit sind, und es gegebenenfalls neu erstellen würde?

Ich verstehe, dass der Hauptvorteil der Verwendung von Zeitstempeln darin besteht, dass keine separate Datenbank verwaltet werden muss, die "früher bekannte" Versionssignaturen verfolgt. Wenn Sie bereit sind, auf diesen Vorteil zu verzichten, warum sollten diese Signaturen dann keine Hashes sein?

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen