Einer der Hauptunterschiede zwischen Git und den meisten anderen Versionskontrollsystemen besteht darin, dass die anderen Commits als eine Reihe von Deltas speichern - Änderungssätze zwischen einem Commit und dem nächsten. Dies erscheint logisch, da es sich um die kleinstmögliche Menge an Informationen handelt, die über ein Commit gespeichert werden müssen. Je länger der Commit-Verlauf dauert, desto mehr Berechnungen sind erforderlich, um die Revisionsbereiche zu vergleichen.
Im Gegensatz dazu speichert Git in jeder Revision einen vollständigen Schnappschuss des gesamten Projekts . Der Grund dafür, dass die Repo-Größe mit jedem Commit nicht dramatisch zunimmt, ist, dass jede Datei im Projekt als Datei im Git-Unterverzeichnis gespeichert wird, die nach dem Hash ihres Inhalts benannt ist. Wenn sich der Inhalt nicht geändert hat, hat sich der Hash nicht geändert, und das Festschreiben zeigt nur auf dieselbe Datei. Und es gibt noch andere Optimierungen.
All dies machte für mich Sinn, bis ich auf diese Informationen über Packdateien stieß , in die Git regelmäßig Daten einfügt, um Platz zu sparen:
Um diesen Platz zu sparen, verwendet Git die Packdatei. Dies ist ein Format, in dem Git nur den Teil speichert, der sich in der zweiten Datei geändert hat, mit einem Zeiger auf die Datei, der es ähnlich ist.
Geht das nicht im Grunde auf die Speicherung von Deltas zurück? Wenn nicht, wie ist es anders? Wie kann vermieden werden, dass Git denselben Problemen ausgesetzt wird, die andere Versionskontrollsysteme haben?
Zum Beispiel verwendet Subversion Deltas, und das Zurücksetzen von 50 Versionen bedeutet, dass 50 Unterschiede rückgängig gemacht werden, während Sie mit Git einfach den entsprechenden Schnappschuss erstellen können. Es sei denn, git speichert auch 50 Unterschiede in den Packdateien ... gibt es einen Mechanismus, der besagt, dass "nach einer kleinen Anzahl von Deltas ein ganz neuer Schnappschuss gespeichert wird", damit wir keinen zu großen Änderungssatz stapeln? Wie könnte Git sonst die Nachteile von Deltas vermeiden?
quelle
Antworten:
Zusammenfassung:
Die Pack-Dateien von Git wurden sorgfältig erstellt, um Festplatten-Caches effektiv zu nutzen und "nette" Zugriffsmuster für allgemeine Befehle und zum Lesen kürzlich referenzierter Objekte bereitzustellen.
Das Pack-Dateiformat von Git ist sehr flexibel (siehe Dokumentation / technisch / pack-format.txt oder The Packfile im Git Community Book ). In den Packdateien werden Objekte auf zwei Arten gespeichert: "undeltifiziert" (nehmen Sie die Rohobjektdaten und entleeren Sie sie) oder "deltifiziert" (bilden Sie ein Delta gegen ein anderes Objekt und entleeren Sie dann die resultierenden Delta-Daten). Die in einer Packung gespeicherten Objekte können in beliebiger Reihenfolge vorliegen (sie müssen (nicht unbedingt) nach Objekttyp, Objektname oder einem anderen Attribut sortiert sein), und deltifizierte Objekte können gegen jedes andere geeignete Objekt desselben Typs erstellt werden.
Der Befehl pack-objects von Git verwendet mehrere Heuristiken , um eine hervorragende Referenzlokalität für allgemeine Befehle bereitzustellen . Diese Heuristiken steuern sowohl die Auswahl der Basisobjekte für deltifizierte Objekte als auch die Reihenfolge der Objekte. Jeder Mechanismus ist größtenteils unabhängig, aber sie teilen einige Ziele.
Git bildet zwar lange Ketten von Delta-komprimierten Objekten, aber die Heuristiken versuchen sicherzustellen, dass sich nur „alte“ Objekte am Ende der langen Ketten befinden. Der Delta-Basis-Cache (dessen Größe von der
core.deltaBaseCacheLimit
Konfigurationsvariablen gesteuert wird ) wird automatisch verwendet und kann die Anzahl der für Befehle, die eine große Anzahl von Objekten lesen müssen (zgit log -p
. B. ), erforderlichen "Neuerstellungen" erheblich reduzieren .Delta-Komprimierungsheuristik
Ein typisches Git-Repository speichert eine sehr große Anzahl von Objekten, sodass es nicht alle vernünftigerweise vergleichen kann, um die Paare (und Ketten) zu finden, die die kleinsten Delta-Darstellungen ergeben.
Die Heuristik zur Auswahl der Delta-Basen basiert auf der Idee, dass die guten Delta-Basen unter Objekten mit ähnlichen Dateinamen und Größen gefunden werden. Jeder Objekttyp wird separat verarbeitet (dh ein Objekt eines Typs wird niemals als Delta-Basis für ein Objekt eines anderen Typs verwendet).
Für die Auswahl der Delta-Basis werden die Objekte (hauptsächlich) nach Dateiname und dann nach Größe sortiert. Ein Fenster in diese sortierte Liste wird verwendet, um die Anzahl der Objekte zu begrenzen, die als potenzielle Delta-Basen betrachtet werden. Wenn für ein Objekt unter den Objekten in seinem Fenster keine "gut genug" 1- Delta-Darstellung gefunden wird, wird das Objekt nicht deltakomprimiert.
Die Größe des Fensters wird durch die
--window=
Optiongit pack-objects
oder diepack.window
Konfigurationsvariable gesteuert . Die maximale Tiefe einer Delta-Kette wird durch die--depth=
Optiongit pack-objects
oder diepack.depth
Konfigurationsvariable gesteuert . Durch die--aggressive
Option,git gc
sowohl die Fenstergröße als auch die maximale Tiefe erheblich zu vergrößern, wird versucht, eine kleinere Packdatei zu erstellen.Die Dateinamensortierung fasst die Objekte für Einträge mit identischen Namen (oder zumindest ähnlichen Endungen (z
.c
. B. )) zusammen. Die Größensortierung ist vom größten zum kleinsten, sodass Deltas, die Daten entfernen, Deltas vorgezogen werden, die Daten hinzufügen (da Entfernungsdeltas kürzere Darstellungen haben) und die früheren, größeren Objekte (normalerweise neuere) in der Regel mit einfacher Komprimierung dargestellt werden.1 Was als „gut genug“ eingestuft wird, hängt von der Größe des betreffenden Objekts und seiner potenziellen Delta-Basis sowie von der Tiefe seiner resultierenden Delta-Kette ab.
Heuristik zur Objektbestellung
Objekte werden in den Packdateien in der Reihenfolge "Zuletzt referenziert" gespeichert. Die Objekte, die zur Rekonstruktion des letzten Verlaufs benötigt werden, werden früher im Paket platziert und liegen nahe beieinander. Dies funktioniert normalerweise gut für OS-Festplatten-Caches.
Alle Festschreibungsobjekte werden nach Festschreibungsdatum (zuletzt zuerst) sortiert und zusammen gespeichert. Diese Platzierung und Reihenfolge optimiert die Festplattenzugriffe, die zum Durchlaufen des Verlaufsdiagramms und zum Extrahieren grundlegender Festschreibungsinformationen (z
git log
. B. ) erforderlich sind .Die Baum- und Blob-Objekte werden beginnend mit dem Baum ab dem ersten gespeicherten (letzten) Commit gespeichert. Jeder Baum wird in der Tiefe zuerst verarbeitet, wobei alle Objekte gespeichert werden, die noch nicht gespeichert wurden. Dadurch werden alle Bäume und Blobs, die für die Rekonstruktion des letzten Commits erforderlich sind, an einem Ort zusammengefasst. Alle Bäume und Blobs, die noch nicht gespeichert wurden, aber für spätere Festschreibungen erforderlich sind, werden als Nächstes in der sortierten Festschreibungsreihenfolge gespeichert.
Die endgültige Objektreihenfolge wird durch die Delta-Basisauswahl geringfügig beeinflusst. Wenn ein Objekt für die Delta-Darstellung ausgewählt wird und sein Basisobjekt noch nicht gespeichert wurde, wird sein Basisobjekt unmittelbar vor dem Delta-Objekt selbst gespeichert. Dies verhindert wahrscheinliche Festplatten-Cache-Fehler aufgrund des nichtlinearen Zugriffs, der zum Lesen eines Basisobjekts erforderlich ist, das "natürlich" später in der Pack-Datei gespeichert worden wäre.
quelle
Die Verwendung von Delta-Speicher in der Pack-Datei ist nur ein Implementierungsdetail. Auf dieser Ebene weiß Git nicht, warum oder wie sich etwas von einer Revision zur nächsten geändert hat, sondern weiß nur, dass Blob B mit Ausnahme dieser Änderungen C Blob A ziemlich ähnlich ist. Daher werden nur Blob A und Änderungen C gespeichert (Wenn dies gewünscht wird, kann auch Blob A und Blob B gespeichert werden.)
Beim Abrufen von Objekten aus der Packdatei wird der Delta-Speicher dem Aufrufer nicht zur Verfügung gestellt. Der Anrufer sieht immer noch vollständige Blobs. Git funktioniert also genauso wie immer ohne die Delta-Speicheroptimierung.
quelle
--depth
Parametergit pack-objects
. Einige, die sich ansehen, was tatsächlich gepackt wird, legen auch nahe, dass Git ein Delta benötigt, das wirklich recht klein ist, damit es sich lohnt, es anstelle des komprimierten Blobs zu verwenden.Wie ich in " Was sind die dünnen Packs von Git? " Erwähnt habe.
Ich habe die Delta-Codierung, die für Pack-Dateien verwendet wird, in " Ist der Git-Binärdiff-Algorithmus (Delta-Speicher) standardisiert? " Ausführlich beschrieben .
Siehe auch " Wann und wie verwendet Git Deltas zur Lagerung? ".
Beachten Sie, dass die
core.deltaBaseCacheLimit
Konfiguration, die die Standardgröße für die Packdatei steuert, für Git 2.0.x / 2.1 (Q3 2014) bald von 16 MB auf 96 MB erhöht wird.Siehe Commit 4874f54 von David Kastrup (Mai 2014):
Bump core.deltaBaseCacheLimit auf 96m
Dies wird mit Git 2.29 (Q4 2020) weiter optimiert, wo "
git index-pack
" ( man ) gelernt hat, deltifizierte Objekte mit größerer Parallelität aufzulösen.Siehe Commit f08cbf6 ( 08.09.2020 ) und Commit ee6f058 , Commit b4718ca , Commit a7f7e84 , Commit 46e6fb1 , Commit fc968e2 , Commit 009be0d (24. August 2020) von Jonathan Tan (
jhowtan
) .(Zusammengeführt von Junio C Hamano -
gitster
- in Commit b7e65b5 , 22. September 2020)quelle