Ich habe eine Zip-Datei mit einer Größe von 1,5 GB.
Der Inhalt ist eine lächerlich große Nur-Text-Datei (60 GB), und ich habe derzeit nicht mehr genügend Speicherplatz auf meiner Festplatte, um alles zu extrahieren, noch möchte ich alles extrahieren, selbst wenn dies der Fall wäre.
Für meinen Anwendungsfall würde es ausreichen, wenn ich Teile des Inhalts einsehen kann.
Daher möchte ich die Datei als Stream entpacken und auf einen Bereich der Datei zugreifen (wie man es bei einer normalen Textdatei per Kopf und Schwanz tun kann).
Entweder nach Speicher (z. B. extrahieren Sie max. 100 KB ab 32 GB) oder nach Zeilen (geben Sie mir die Klartextzeilen 3700-3900).
Gibt es einen Weg, das zu erreichen?
text-processing
zip
k0pernikus
quelle
quelle
Antworten:
Beachten Sie, dass Dateien
gzip
extrahiert werdenzip
können (mindestens der erste Eintrag in derzip
Datei). Wenn sich also nur eine große Datei in diesem Archiv befindet, können Sie Folgendes tun:Zum Beispiel, um die 20 Zeilen zu extrahieren, die mit der 3000er beginnen.
Oder:
Für das Gleiche mit Bytes (vorausgesetzt eine
head
Implementierung, die dies unterstützt-c
).Für jedes beliebige Mitglied im Archiv auf eine Unixy-Weise:
Mit dem
head
Built-In vonksh93
(wie wenn/opt/ast/bin
es voraus ist$PATH
) können Sie auch Folgendes tun:Beachten Sie, dass in jedem Fall
gzip
/bsdtar
/unzip
immer dekomprimieren müssen (und Verwerfungs hier) der gesamte Abschnitt der Datei , dass führt zu dem Teil , die Sie extrahieren möchten. Das hängt davon ab, wie der Komprimierungsalgorithmus funktioniert.quelle
gzip
es sich leisten kann, werden die anderen „z aware“ Dienstprogramme (zcat
,zless
usw.) auch arbeiten?gzip
(im Allgemeinen gilt daszless
, nicht unbedingt, vonzcat
denen auf einigen Systemen immer noch.Z
nur Dateien gelesen werden sollen), ja.Eine Lösung mit unzip -p und dd, zum Beispiel um 10kb mit 1000 Blöcken Versatz zu extrahieren:
Hinweis: Ich habe das nicht mit wirklich riesigen Daten versucht ...
quelle
unzip -l ARCHIVE
den Archivinhalt auflisten undunzip -p ARCHIVE PATH
den Inhalt eines einzelnen ObjektsPATH
zu stdout extrahieren .dd
von Pipes mit count oder skip unzuverlässig, da dies vieleread()
s von bis zu 1024 Bytes ermöglicht. Es ist also nur dann gewährleistet, dass es richtig funktioniert, wennunzip
Brocken geschrieben werden, deren Größe ein Vielfaches von 1024 ist.Wenn Sie die Kontrolle über die Erstellung dieser großen ZIP-Datei haben, können Sie eine Kombination aus
gzip
und verwendenzless
.Auf diese Weise können Sie
zless
den Inhalt der Datei als Pager verwenden und anzeigen, ohne sich um das Extrahieren kümmern zu müssen.Wenn Sie das Komprimierungsformat nicht ändern können, funktioniert dies offensichtlich nicht. Wenn ja, fühle ich mich
zless
eher günstig.quelle
Um bestimmte Zeilen der Datei anzuzeigen, leiten Sie die Ausgabe an den Unix-Stream-Editor sed weiter . Dadurch können beliebig große Datenströme verarbeitet und sogar zum Ändern der Daten verwendet werden. Führen Sie Folgendes aus, um die Zeilen 3700-3900 wie gewünscht anzuzeigen.
quelle
sed -n 3700,3900p
wird bis zum Ende der Datei weiterlesen. Es ist besser, diessed '3700,$!d;3900q'
zu vermeiden oder sogar generell effizienter:tail -n +3700 | head -n 201
Ich fragte mich, ob es möglich war, etwas effizienteres zu tun, als vom Anfang der Datei bis zum Ende zu dekomprimieren. Es scheint, dass die Antwort nein ist. Bei einigen CPUs (Skylake)
zcat | tail
wird die CPU jedoch nicht auf die volle Taktrate hochgefahren. Siehe unten. Ein benutzerdefinierter Decoder könnte dieses Problem umgehen und die Aufrufe des Pipe-Schreibsystems speichern und möglicherweise ~ 10% schneller sein. (Oder ~ 60% schneller bei Skylake, wenn Sie die Energieverwaltungseinstellungen nicht anpassen).Das Beste, was Sie mit einer angepassten zlib mit einer
skipbytes
Funktion tun können, ist, die Symbole in einem Komprimierungsblock zu analysieren, um zum Ende zu gelangen, ohne den dekomprimierten Block tatsächlich zu rekonstruieren. Dies könnte erheblich schneller sein (wahrscheinlich mindestens 2x) als der Aufruf der regulären Dekodierungsfunktion von zlib, um denselben Puffer zu überschreiben und in der Datei vorwärts zu gehen. Aber ich weiß nicht, ob jemand eine solche Funktion geschrieben hat. (Und ich denke, das funktioniert nicht wirklich, es sei denn, die Datei wurde speziell geschrieben, damit der Decoder an einem bestimmten Block neu gestartet werden kann.)Ich hatte gehofft, es gäbe eine Möglichkeit, Deflate-Blöcke zu überspringen, ohne sie zu dekodieren, denn das wäre viel schneller. Der Huffman-Baum wird zu Beginn eines jeden Blocks gesendet, so dass Sie von Beginn eines jeden Blocks aus decodieren können (glaube ich). Oh, ich denke, der Decoder-Status ist mehr als der Huffman-Baum, es sind auch die vorherigen 32 kB decodierter Daten, und dies wird standardmäßig nicht über Blockgrenzen hinweg zurückgesetzt / vergessen. Dieselben Bytes können wiederholt referenziert werden, sodass sie in einer riesigen komprimierten Datei möglicherweise nur einmal buchstäblich vorkommen. (zB in einer Protokolldatei bleibt der Hostname im Komprimierungswörterbuch wahrscheinlich die ganze Zeit "heiß", und jede Instanz davon verweist auf die vorherige, nicht auf die erste).
Das
zlib
Handbuch besagt, dass SieZ_FULL_FLUSH
beim Aufrufen verwenden müssen,deflate
wenn der komprimierte Stream bis zu diesem Punkt durchsucht werden soll. Es "setzt den Komprimierungsstatus zurück", also denke ich, ohne das können Rückwärtsreferenzen in den / die vorherigen Block (e) gehen. Wenn Ihre ZIP-Datei also nicht mit gelegentlichen Full-Flush-Blöcken geschrieben wurde (wie jedes 1G oder etwas, das die Komprimierung vernachlässigbar beeinträchtigt), müssten Sie bis zu dem von Ihnen gewünschten Zeitpunkt mehr Dekodierungsarbeit leisten als ursprünglich Denken. Ich denke, Sie können wahrscheinlich nicht am Anfang eines Blocks beginnen.Der Rest wurde geschrieben, während ich dachte, es wäre möglich, einfach den Anfang des Blocks zu finden, der das erste Byte enthält, das Sie wollen, und von dort zu dekodieren.
Leider gibt der Start eines Deflate-Blocks bei komprimierten Blöcken nicht an, wie lange er dauert . Inkomprimierbare Daten können mit einem unkomprimierten Blocktyp codiert werden, der vorne eine 16-Bit-Größe in Byte aufweist, komprimierte Blöcke jedoch nicht: RFC 1951 beschreibt das Format recht gut lesbar . Blöcke mit dynamischer Huffman-Codierung haben den Baum am Anfang des Blocks (damit der Dekomprimierer nicht im Stream suchen muss), daher muss der Komprimierer den gesamten (komprimierten) Block im Speicher behalten, bevor er ihn schreibt.
Die maximale Rückwärtsreferenzdistanz beträgt nur 32 kB, sodass der Kompressor nicht viele unkomprimierte Daten im Speicher behalten muss, die Blockgröße jedoch nicht begrenzt. Blöcke können mehrere Megabyte lang sein. (Dies ist groß genug, damit sich die Suche nach einer Festplatte auch auf einem Magnetlaufwerk lohnt, im Gegensatz zum sequentiellen Einlesen in den Speicher und zum Überspringen von Daten im RAM, wenn das Ende des aktuellen Blocks gefunden werden konnte, ohne ihn zu analysieren.)
zlib macht Blöcke so lang wie möglich: Laut Marc Adler startet zlib einen neuen Block erst dann, wenn der Symbolpuffer voll ist. In der Standardeinstellung sind dies 16.383 Symbole (Literale oder Übereinstimmungen).
Ich habe den Ausgang von gzippt
seq
(der extrem redundant ist und daher wahrscheinlich kein großartiger Test), aberpv < /tmp/seq1G.gz | gzip -d | tail -c $((1024*1024*1000)) | wc -c
auf einem Skylake i7-6700k mit 3,9 GHz und DDR4-2666 RAM läuft dieser mit nur ~ 62 MiB / s komprimierter Daten. Das sind 246 MB / s dekomprimierter Daten. Dies entspricht einer Änderung der Datenmenge im Vergleich zu einermemcpy
Geschwindigkeit von ~ 12 GB / s bei Blockgrößen, die zu groß sind, um in den Cache zu passen.(Mit
energy_performance_preference
der Standardeinstellungbalance_power
anstelle vonbalance_performance
wird der interne CPU-Governor von Skylake nur mit 2,7 GHz und einer komprimierten Datenrate von ~ 43 MiB / s betrieben. Ichsudo sh -c 'for i in /sys/devices/system/cpu/cpufreq/policy[0-9]*/energy_performance_preference;do echo balance_performance > "$i";done'
optimiere ihn. Wahrscheinlich sehen solche häufigen Systemaufrufe nicht wie echte CPU-gebunden aus.) arbeiten an der Power-Management-Einheit.)TL: DR:
zcat | tail -c
ist CPU-gebunden, auch bei einer schnellen CPU, es sei denn, Sie haben sehr langsame Festplatten. gzip verwendete 100% der CPU, auf der es lief (und laut Angaben 1,81 Anweisungen pro Taktperf
), undtail
verwendete 0,162 der CPU, auf der es lief (0,58 IPC). Das System war sonst meist im Leerlauf.Ich verwende Linux 4.14.11-1-ARCH, bei dem KPTI standardmäßig aktiviert ist , um Meltdown zu umgehen. Daher sind all diese
write
Systemaufrufegzip
teurer als früher: /Wenn die Suchfunktion in
unzip
oder integriert istzcat
(aber weiterhin die regulärezlib
Dekodierungsfunktion verwendet) , werden alle diese Pipe-Schreibvorgänge gespeichert, und Skylake-CPUs werden mit voller Taktgeschwindigkeit ausgeführt. (Dieses Downclocking für einige Arten von Lasten ist nur für Intel Skylake und höher verfügbar, da diese die CPU-Frequenzentscheidung vom Betriebssystem trennen, da sie mehr Daten über die CPU-Aktivitäten haben und schneller hoch- und runterfahren können. Dies ist der Fall.) normalerweise gut, aber hier führt dies dazu, dass Skylake mit einer konservativeren Einstellung des Reglers nicht auf Hochtouren läuft.Keine Systemaufrufe, nur das Umschreiben eines Puffers, der in den L2-Cache passt, bis Sie die gewünschte Start-Byte-Position erreicht haben, würde wahrscheinlich mindestens ein paar Prozent Unterschied bewirken. Vielleicht sogar 10%, aber ich mache hier nur Zahlen. Ich habe kein
zlib
detailliertes Profil erstellt, um festzustellen, wie groß der Cache-Speicherbedarf ist und wie stark das Leeren des TLB (und damit das Leeren des UOP-Caches) bei jedem Systemaufruf mit aktiviertem KPTI schmerzt.Es gibt einige Softwareprojekte, die dem gzip-Dateiformat einen Suchindex hinzufügen . Dies hilft Ihnen nicht, wenn Sie niemanden dazu bringen können, suchbare komprimierte Dateien für Sie zu generieren, aber andere zukünftige Leser können davon profitieren.
Vermutlich keines dieser Projekte haben eine Dekodierungsfunktion , die weiß , wie durch einen Deflate Strom ohne Index zu überspringen, weil sie nur an der Arbeit entworfen sind , wenn ein Index ist verfügbar.
quelle
Sie können die Zip-Datei in einer Python-Sitzung
zf = zipfile.ZipFile(filename, 'r', allowZip64=True)
öffnen. Wenn Sie diese geöffnet haben, können Sie jede Datei im Zip-Archiv zum Lesen öffnen und Zeilen usw. daraus lesen, als ob es sich um eine normale Datei handeln würde.quelle