Modo più rapido ed efficiente per ottenere il numero di record (righe) in un file compresso con gzip


16

Sto provando a fare un conteggio dei record su un file gzip da 7,6 GB. Ho trovato alcuni approcci usando il zcatcomando.

$ zcat T.csv.gz | wc -l
423668947

Funziona ma richiede troppo tempo (più di 10 minuti per ottenere il conteggio). Ho provato alcuni altri approcci come

$ sed -n '$=' T.csv.gz
28173811
$ perl -lne 'END { print $. }' < T.csv.gz
28173811
$ awk 'END {print NR}' T.csv.gz
28173811

Tutti e tre questi comandi si stanno eseguendo abbastanza velocemente ma dando un conteggio errato di 28173811.

Come posso eseguire un conteggio dei record in un tempo minimo?


5
Perché è necessario contare il numero di record? Se stai provando a contarli prima di elaborarli, significa che devi decomprimere il file due volte.
Andrew Henle,

3
Maggiori informazioni sul perché lo stai facendo sarebbero utili. Se è in corso qualcosa, ovvero comprimi regolarmente un mucchio di file e in un secondo momento devi conoscere il numero di record, perché non contarli mentre sono compressi e incorporare il numero nel nome del file?
jamesqf,

3
La lettura di un file da 9,7 GB da un disco meccanico è intrinsecamente più lenta. Memorizza il file su un SSD e vedi quanto più velocemente gira gunzip / zcat. Ma come dice @jamesqf, memorizzare il linecount nel nome del file o in un file nel tgz ed estrarre quel file sarà molto più veloce.
ChuckCottrill,

2
Ci sono buone ragioni teoriche per cui non puoi evitare questo lavoro. Un formato di compressione che consente di determinare alcune proprietà utili dei dati "senza decomprimerlo" è praticamente per definizione un formato di compressione non buono come potrebbe essere :)
hobbs

Risposte:


28

I sed, perle awkcomandi che si possono citare corrette, ma tutti leggere i compressi dati e conta caratteri newline in quello. Questi caratteri di nuova riga non hanno nulla a che fare con i caratteri di nuova riga nei dati non compressi.

Per contare il numero di righe nei dati non compressi, non è possibile decomprimerlo. Il tuo approccio con zcatè l'approccio corretto e dal momento che i dati è così grande, che si prende il tempo per decomprimerlo.

La maggior parte delle utility che si occupano di gzipcompressione e decompressione molto probabilmente useranno le stesse routine di libreria condivise per farlo. L'unico modo per accelerarlo sarebbe quello di trovare un'implementazione delle zlibroutine che siano in qualche modo più veloci di quelle predefinite, e ricostruire ad esempio zcatper usarle.


11
Sarebbe un esercizio di programmazione non banale, ma fattibile. Il punto è non ricostruire zcat. Una parte significativa del lavoro di zcatsta generando l'output effettivo. Ma se stai contando solo i \npersonaggi, non è necessario. gzipla compressione funziona essenzialmente sostituendo le stringhe lunghe comuni con stringhe più brevi. Quindi devi solo preoccuparti delle lunghe stringhe nel dizionario che contengono una \ne contare l'occorrenza (ponderata) di quelle. Ad esempio a causa delle regole inglesi, .\nè una stringa comune a 16 bit.
MSalters,

19

Usa unpigz.

La risposta di Kusalananda è corretta, sarà necessario decomprimere che tutto il file per la scansione dei suoi contenuti. /bin/gunziplo fa il più velocemente possibile, su un singolo core. Pigz è un'implementazione parallela gzipche può utilizzare più core.

Purtroppo, la decompressione stessa della normale file gzip non può essere parallelized, ma pigznon offre una versione migliorata di gunzip, unpigz, che fa lavoro relativo, come la lettura, la scrittura, e checksum in un thread separato. In alcuni benchmark rapidi, unpigzè quasi due volte più veloce rispetto gunzipalla mia macchina i5 core.

Installa pigzcon il tuo gestore pacchetti preferito e usa unpigzinvece di gunzip, o unpigz -cinvece di zcat. Quindi il tuo comando diventa:

$ unpigz -c T.csv.gz | wc -l

Tutto ciò presuppone che il collo di bottiglia sia la CPU, non il disco, ovviamente.


4
La mia pigzpagina man afferma che la decompressione non può essere parallelizzata, almeno non senza flussi di deflazione appositamente preparati a tale scopo. Di conseguenza, pigz utilizza un singolo thread (il thread principale) per la decompressione, ma creerà altri tre thread per la lettura, la scrittura e il controllo del calcolo, che possono accelerare la decompressione in alcune circostanze . Tuttavia, come te, trovo che sia almeno il doppio più veloce di gzip, se non a causa del parallelismo
Stéphane Chazelas,

@ StéphaneChazelas Ottimo punto! Ciò spiega l'accelerazione lievemente deludente per la decompressione. Ho modificato il mio post per riflettere meglio queste informazioni.
marcelm

5

Il problema con tutte le condutture è che stai sostanzialmente raddoppiando il lavoro. Non importa quanto sia veloce la decompressione, i dati devono comunque essere trasferiti a un altro processo.

Perl ha PerlIO :: gzip che ti permette di leggere direttamente i flussi gzip. Pertanto, potrebbe offrire un vantaggio anche se la sua velocità di decompressione potrebbe non corrispondere a quella di unpigz:

#!/usr/bin/env perl

use strict;
use warnings;

use autouse Carp => 'croak';
use PerlIO::gzip;

@ARGV or croak "Need filename\n";

open my $in, '<:gzip', $ARGV[0]
    or croak "Failed to open '$ARGV[0]': $!";

1 while <$in>;

print "$.\n";

close $in or croak "Failed to close '$ARGV[0]': $!";

L'ho provato con un file compresso gzip da 13 MB (si decomprime a 1,4 GB) su un vecchio MacBook Pro del 2010 con 16 GB di RAM e un vecchio ThinkPad T400 con 8 GB di RAM con il file già nella cache. Su Mac, lo script Perl è stato significativamente più veloce rispetto all'utilizzo di pipeline (5 secondi contro 22 secondi), ma su ArchLinux ha perso unpigz:

$ time -p ./gzlc.pl spy.gz 
1154737
reale 4.49
utente 4.47
sys 0.01

contro

$ time -p unpigz -c spy.gz | wc -l
1154737
3.68 reale
utente 4.10
sys 1.46

e

$ time -p zcat spy.gz | wc -l
1154737
reale 6.41
utente 6.08
sys 0.86

Chiaramente, usando unpigz -c file.gz | wc -lè il vincitore qui sia in termini di velocità. E quella semplice riga di comando batte sicuramente la scrittura di un programma, per quanto breve.


1
Penso che tu stia sopravvalutando notevolmente le risorse necessarie per spostare i dati tra due processi, rispetto ai calcoli di decompressione. Prova a confrontare i vari approcci;)
marcelm

2
@ SinanÜnür Sul mio sistema x86_64 Linux (anche vecchio hardware) gzip | wcha la stessa velocità del tuo script perl. Ed pigz | wcè il doppio più veloce. gzipfunziona con la stessa velocità, indipendentemente dal fatto che scrivo l'output in / dev / null o pipe in wcQuello che credo sia che la "libreria gzip" utilizzata da Perl sia più veloce dello strumento da riga di comando gzip. Forse c'è un altro problema specifico di Mac / Darwin con le pipe. È ancora sorprendente che questa versione perl sia affatto competitiva.
rudimeier,

1
Sulla mia installazione di Linux x86_64, sembra fare meglio di zcate peggio di unpigz. Sono sorpreso di quanto sia più veloce la pipeline sul sistema Linux rispetto al Mac. Non me lo aspettavo, anche se avrei dovuto, come ho osservato una volta, che lo stesso programma funzionasse più velocemente su una VM Linux con CPU limitata sullo stesso Mac che su bare metal.
Sinan Ünür

1
Interessante; sul mio sistema (Debian 8.8 amd64, quad core i5), lo script perl è leggermente più lento ... 109M .gz decomprime file a 1.1G di testo, richiede costantemente 5,4 secondi zcat | wc -le 5,5 secondi per il tuo script perl. Onestamente, sono stupito della variazione che le persone segnalano qui, specialmente tra Linux e MacOS X!
marcelm

Non so se posso generalizzare quello che vedo sul mio Mac, sta succedendo qualcosa di strano. Con il file decompresso da 1,4 GB, wc -loccorrono 2,5 secondi. gzcat compressed.gz > /dev/nulldura 2,7 secondi. Tuttavia, la pipeline richiede 22 secondi. Se provo GNU wc, ci vuole solo mezzo secondo sul file decompresso, ma 22 secondi nella pipeline. GNU zcatrichiede il doppio del tempo per l'esecuzione zcat compressed.gz > /dev/null. Questo è su Mavericks, vecchia CPU Core 2 Duo, 16 GB di RAM, SSD Crucial MX100.
Sinan Ünür

4

La risposta di Kusalananda è per lo più corretta. Per contare le righe devi cercare nuove righe. Tuttavia è teoricamente possibile cercare nuove righe senza decomprimere completamente il file.

gzip utilizza la compressione DEFLATE. DEFLATE è una combinazione di LZ77 e codifica Huffman. Potrebbe esserci un modo per capire solo il nodo del simbolo di Huffman per newline e ignorare il resto. C'è quasi sicuramente un modo per cercare nuove righe codificate usando L277, mantenere un conteggio di byte e ignorare tutto il resto.

Quindi IMHO è teoricamente possibile trovare una soluzione più efficiente di unpigz o zgrep. Detto questo, non è certamente pratico (a meno che qualcuno non l'abbia già fatto).


7
Un grosso problema con questa idea è che i simboli Huffman usati da DEFLATE corrispondono alle sequenze di bit dopo la compressione LZ77, quindi potrebbe non esserci una semplice relazione tra loro e i caratteri U + 000A nel file non compresso. Ad esempio, forse un simbolo di Huffman significa gli ultimi cinque bit di "." seguito dai primi tre bit di "\ n" e un altro simbolo indica gli ultimi cinque bit di "\ n" seguito da tutti gli otto bit di "T".
zwol,

@zwol No, la parte LZ77 dell'algoritmo Deflate comprime le sequenze di byte, non le sequenze di bit. en.wikipedia.org/wiki/DEFLATE#Duplicate_string_elimination
Ross Ridge

1
@RossRidge Huh, non lo sapevo, ma non credo che invalidi ciò che ho detto. The Huffman simboli di possono, mi sembra basato sul prossimo paragrafo di quel riferimento, ognuno espandersi in un numero variabile di bit, non devono produrre un numero intero di byte.
zwol,

1
@zwol Certo, devi cercare le sequenze di bit di codice Huffman corrispondenti nel flusso di bit, ma questa risposta non suggerisce diversamente. Il problema con questa risposta è che determinare quali codici Huffman alla fine generano o più caratteri newline non è semplice. I codici LZ77 che generano nuove righe cambiano costantemente man mano che la finestra scorrevole si sposta, il che significa che cambiano anche i codici Huffman. Dovresti implementare l'intero algoritmo di decompressione tranne la parte di output, e forse una parte della finestra scorrevole poiché sei interessato solo alle nuove righe.
Ross Ridge,

1

Può essere fatto usando zgrepcon -cflag e $parametro.

In questo caso -c indica al comando di produrre il numero di righe corrispondenti e che regex $ corrisponde alla fine della riga in modo che corrisponda a ogni riga o file.

zgrep -c $ T.csv.gz 

Come commentato da @ StéphaneChazelas - zgrep è solo uno script in circolazione zcate grepdovrebbe fornire prestazioni simili al suggerimento originale dizcat | wc -l


2
Ciao Yaron, grazie per la risposta, anche se zgrep sta impiegando tutto il tempo necessario a zcat, ho bisogno di trovare un altro approccio, penso
Rahul,

8
zgrepè generalmente uno script che invoca zcat(lo stesso di gzip -dcq) per decomprimere i dati e fornirli grep, quindi non sarà di aiuto.
Stéphane Chazelas,

1
@ StéphaneChazelas - grazie per il commento, aggiorna la mia risposta per riflettere.
Yaron,

0

Come puoi vedere, la maggior parte delle risposte cerca di ottimizzare ciò che può: il numero di cambi di contesto e I / O tra processi. Il motivo è che questo è l'unico cosa che puoi ottimizzare facilmente qui.

Ora il problema è che la sua necessità di risorse è quasi trascurabile per la necessità di risorse della decompressione. Questo è il motivo per cui le ottimizzazioni non renderanno davvero nulla più veloce.

Dove potrebbe essere realmente accelerato, sarebbe un algoritmo un-gzip (ovvero decompressione) modificato, che elimina la produzione effettiva del flusso di dati decompresso; piuttosto calcola solo il numero di newline nel flusso decompresso da quello compresso . Sarebbe difficile, richiederebbe la profonda conoscenza dell'algoritmo di gzip (una combinazione di LZW e algoritmi di compressione Huffman ). È abbastanza probabile che l'algoritmo non consenta di ottimizzare in modo significativo il tempo di decompressione con il fulmine, che abbiamo solo bisogno di conoscere i conteggi di newline. Anche se fosse possibile, in sostanza dovrebbe essere stata sviluppata una nuova libreria di decompressione gzip (non esiste fino a che non lo sapremo).

La risposta realistica alla tua domanda è che no, non puoi renderla significativamente più veloce.

Forse potresti usare una decompressione gzip parallelizzata, se esiste. Potrebbe utilizzare più core della CPU per la decompressione. Se non esiste, potrebbe essere sviluppato relativamente facilmente.

Per xz , esiste un compressore parallelo (pxz).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.