Le moyen le plus rapide et le plus efficace pour obtenir le nombre d'enregistrements (lignes) dans un fichier compressé avec gzip

16

J'essaie de faire un nombre record sur un fichier gzip de 7,6 Go. J'ai trouvé peu d'approches en utilisant la zcatcommande.

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

Cela fonctionne mais cela prend trop de temps (plus de 10 minutes pour obtenir le décompte). J'ai essayé quelques autres approches comme

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

Ces trois commandes s'exécutent assez rapidement mais donnent un nombre incorrect de 28173811.

Comment effectuer un décompte d'enregistrements en un minimum de temps?

Rahul
la source
5
Pourquoi avez-vous besoin de compter le nombre d'enregistrements? Si vous essayez de les compter avant de les traiter, cela signifie que vous devez décompresser le fichier deux fois.
Andrew Henle
3
Plus d'informations sur les raisons pour lesquelles vous faites cela seraient utiles. Si c'est quelque chose en cours - c'est-à-dire que vous compressez régulièrement un tas de fichiers et que vous avez besoin de connaître le nombre d'enregistrements ultérieurement - pourquoi ne pas les compter lorsqu'ils sont compressés et incorporer le nombre dans le nom de fichier?
jamesqf
3
La lecture d'un fichier de 9,7 Go à partir d'un disque mécanique est intrinsèquement plus lente. Stockez le fichier sur un SSD et voyez à quelle vitesse le gunzip / zcat s'exécute. Mais comme le dit @jamesqf, stockez le linecount dans le nom de fichier, ou dans un fichier dans le tgz, et l'extraction de ce fichier sera beaucoup plus rapide.
ChuckCottrill
2
Il y a de bonnes raisons théoriques pour lesquelles vous ne pouvez pas éviter ce travail. Un format de compression qui vous permet de déterminer certaines propriétés utiles des données "sans les décompresser" n'est pas par définition un format de compression aussi bon qu'il pourrait l'être :)
hobbs

Réponses:

28

Les sed, perlet les awkcommandes que vous mentionnez peuvent être correctes, mais ils lu tous les comprimés de données et compte des sauts de ligne dans ce. Ces caractères de nouvelle ligne n'ont rien à voir avec les caractères de nouvelle ligne des données non compressées.

Pour compter le nombre de lignes dans les données non compressées, il est impossible de les décompresser. Votre approche avec zcatest la bonne approche et comme les données sont si volumineuses, il faudra du temps pour les décompresser.

La plupart des utilitaires traitant de la gzipcompression et de la décompression utiliseront très probablement les mêmes routines de bibliothèque partagée pour ce faire. La seule façon de l'accélérer serait de trouver une implémentation des zlibroutines plus rapides que celles par défaut, et de reconstruire par exemple zcatpour les utiliser.

Kusalananda
la source
11
Ce serait un exercice de programmation non trivial, mais réalisable. Le tout est de ne pas reconstruire zcat. Une partie importante du travail de zcatgénère la sortie réelle. Mais si vous ne comptez que des \ncaractères, ce n'est pas nécessaire. gzipla compression fonctionne essentiellement en remplaçant les chaînes longues courantes par des chaînes plus courtes. Il vous suffit donc de vous préoccuper des longues chaînes du dictionnaire qui contiennent un \net de compter leur occurrence (pondérée). Par exemple, en raison des règles anglaises, .\nest une chaîne commune de 16 bits.
MSalters
19

Utilisez unpigz.

La réponse de Kusalananda est correcte, vous aurez besoin de décompresser le fichier entier pour analyser son contenu. /bin/gunziple fait aussi vite que possible, sur un seul cœur. Pigz est une implémentation parallèle gzipqui peut utiliser plusieurs cœurs.

Malheureusement, la décompression de fichiers lui - même gzip normal ne peut pas être parallélisés, mais pigzne offre une version améliorée gunzip, unpigzqui fait des travaux connexes tels que la lecture, l' écriture et la somme de contrôle dans un thread séparé. Dans certains tests rapides, unpigzest presque deux fois plus rapide que gunzipsur ma machine Core i5.

Installez pigzavec votre gestionnaire de paquets préféré et utilisez unpigzau lieu de gunzipou unpigz -cau lieu de zcat. Votre commande devient donc:

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

Tout cela suppose que le goulot d'étranglement est le CPU, pas le disque, bien sûr.

marcelm
la source
4
Ma pigzpage de manuel indique que la décompression ne peut pas être parallélisée, du moins pas sans des flux de dégonflage spécialement préparés à cet effet. Par conséquent, pigz utilise un seul thread (le thread principal) pour la décompression, mais créera trois autres threads pour la lecture, l'écriture et le calcul de vérification, ce qui peut accélérer la décompression dans certaines circonstances . Pourtant, comme vous, je trouve que c'est au moins deux fois plus rapide que gzip, sinon à cause du parallélisme
Stéphane Chazelas
@ StéphaneChazelas Bon point! Cela explique l'accélération légèrement décevante de la décompression. J'ai modifié mon message pour mieux refléter ces informations.
marcelm
5

Le problème avec tous les pipelines, c'est que vous doublez essentiellement le travail. Quelle que soit la rapidité de la décompression, les données doivent toujours être transférées vers un autre processus.

Perl a PerlIO :: gzip qui vous permet de lire directement les flux gzippés. Par conséquent, il pourrait offrir un avantage même si sa vitesse de décompression peut ne pas correspondre à celle de 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]': $!";

Je l'ai essayé avec un fichier compressé gzip de 13 Mo (décompresse à 1,4 Go) sur un ancien MacBook Pro 2010 avec 16 Go de RAM et un vieux ThinkPad T400 avec 8 Go de RAM avec le fichier déjà dans le cache. Sur Mac, le script Perl était significativement plus rapide que l'utilisation de pipelines (5 secondes contre 22 secondes), mais sur ArchLinux, il a perdu à unpigz:

$ time -p ./gzlc.pl spy.gz 
1154737
réel 4,49
utilisateur 4.47
sys 0,01

contre

$ time -p unpigz -c spy.gz | wc -l
1154737
réel 3,68
utilisateur 4.10
sys 1.46

et

$ time -p zcat spy.gz | wc -l
1154737
réel 6,41
utilisateur 6.08
sys 0.86

De toute évidence, l'utilisation unpigz -c file.gz | wc -lest la gagnante ici en termes de vitesse. Et, cette simple ligne de commande bat sûrement l'écriture d'un programme, aussi court soit-il.

Sinan Ünür
la source
1
Je pense que vous surestimez considérablement les ressources nécessaires pour déplacer les données entre deux processus, par rapport aux calculs de décompression. Essayez de comparer les différentes approches;)
marcelm
2
@ SinanÜnür Sur mon système Linux x86_64 (également l'ancien matériel) gzip | wca la même vitesse que votre script perl. Et pigz | wcc'est deux fois plus rapide. gzipfonctionne à la même vitesse, peu importe si j'écris la sortie dans / dev / null ou pipe dans wcCe que je crois, c'est que la "bibliothèque gzip" utilisée par perl est plus rapide que l'outil de ligne de commande gzip. Il y a peut-être un autre problème spécifique Mac / Darwin avec les tuyaux. Il est toujours étonnant que cette version de perl soit compétitive.
rudimeier
1
Sur mon installation Linux x86_64, il semble faire mieux que zcatet pire que unpigz. Je suis étonné de voir à quel point le pipeline est plus rapide sur le système Linux que sur le Mac. Je ne m'attendais pas à cela, même si j'aurais dû, comme je l'ai déjà observé, que le même programme s'exécutait plus rapidement sur une machine virtuelle Linux à processeur limité sur ce même Mac que sur du métal nu.
Sinan Ünür
1
C'est intéressant; sur mon système (Debian 8.8 amd64, quad core i5), le script perl est légèrement plus lent ... Le fichier .gz de 109 Mo se décompresse en 1,1 G de texte, prend constamment 5,4 secondes pour zcat | wc -let 5,5 secondes pour votre script perl. Honnêtement, je suis étonné de la variation que les gens rapportent ici, en particulier entre Linux et MacOS X!
marcelm
Je ne sais pas si je peux généraliser ce que je vois sur mon Mac, quelque chose d'étrange se passe. Avec le fichier 1,4 Go décompressé, cela wc -lprend 2,5 secondes. gzcat compressed.gz > /dev/nullprend 2,7 secondes. Pourtant, le pipeline prend 22 secondes. Si j'essaie GNU wc, cela ne prend qu'une demi-seconde sur le fichier décompressé, mais 22 secondes dans le pipeline. GNU zcatprend deux fois plus de temps à s'exécuter zcat compressed.gz > /dev/null. C'est sur Mavericks, ancien processeur Core 2 Duo, 16 Go de RAM, SSD Crucial MX100.
Sinan Ünür
4

La réponse de Kusalananda est généralement correcte. Pour compter les lignes, vous devez rechercher des sauts de ligne. Cependant, il est théoriquement possible de rechercher des sauts de ligne sans décompresser complètement le fichier.

gzip utilise la compression DEFLATE. DEFLATE est une combinaison de l'encodage LZ77 et Huffman. Il peut y avoir un moyen de comprendre uniquement le nœud de symbole Huffman pour la nouvelle ligne et d'ignorer le reste. Il existe presque certainement un moyen de rechercher des sauts de ligne codés à l'aide de L277, de conserver un nombre d'octets et d'ignorer tout le reste.

Donc, à mon humble avis, il est théoriquement possible de trouver une solution plus efficace que unpigz ou zgrep. Cela étant dit, ce n'est certainement pas pratique (à moins que quelqu'un ne l'ait déjà fait).

IAmBarry
la source
7
Un problème majeur avec cette idée est que les symboles Huffman utilisés par DEFLATE correspondent à des séquences de bits après compression LZ77, il peut donc n'y avoir aucune relation simple entre eux et les caractères U + 000A dans le fichier non compressé. Par exemple, peut-être qu'un symbole Huffman signifie les cinq derniers bits de "." suivi des trois premiers bits de "\ n", et un autre symbole signifie les cinq derniers bits de "\ n" suivis des huit bits de "T".
zwol
@zwol Non, la partie LZ77 de l'algorithme Deflate compresse les séquences d'octets, pas les séquences de bits. en.wikipedia.org/wiki/DEFLATE#Duplicate_string_elimination
Ross Ridge
1
@ RossRidge Huh, je ne le savais pas, mais je ne pense pas que cela invalide ce que j'ai dit. Les symboles Huffman peuvent, il me semble basé sur le paragraphe suivant de cette référence, chacun s'étendre à un nombre variable de bits, ils n'ont pas à produire un nombre entier d'octets.
zwol
1
@zwol Bien sûr, vous devez rechercher des séquences de bits de code Huffman correspondantes dans le flux binaire, mais cette réponse ne suggère pas le contraire. Le problème avec cette réponse est que déterminer quels codes Huffman génèrent finalement ou plus de caractères de nouvelle ligne n'est pas simple. Les codes LZ77 qui génèrent des nouvelles lignes changent constamment à mesure que la fenêtre coulissante se déplace, ce qui signifie que les codes Huffman changent également. Vous devez implémenter l'intégralité de l'algorithme de décompression à l'exception de la partie de sortie, et peut-être une partie de la fenêtre coulissante car vous n'êtes intéressé que par les nouvelles lignes.
Ross Ridge,
1

Peut être fait en utilisant zgrepun -cindicateur et un $paramètre.

Dans ce cas, -c indique à la commande de sortir le nombre de lignes correspondantes et l'expression régulière $ correspond à la fin de la ligne pour correspondre à chaque ligne ou au fichier.

zgrep -c $ T.csv.gz 

Comme l'a commenté @ StéphaneChazelas - zgrep est seulement autour d' un script zcatet grepil devrait fournir des performances similaires à la suggestion originale dezcat | wc -l

Yaron
la source
2
Salut Yaron merci pour la réponse même le zgrep prend autant de temps que zcat j'ai besoin de trouver une autre approche je pense
Rahul
8
zgrepest généralement un script qui invoque zcat(comme gzip -dcq) pour décompresser les données et les alimenter grep, donc ça ne va pas aider.
Stéphane Chazelas
1
@ StéphaneChazelas - merci pour le commentaire, mettez à jour ma réponse pour la refléter.
Yaron
0

Comme vous pouvez le voir, la plupart des réponses essaient d'optimiser ce qu'il peut: le nombre de commutateurs de contexte et d'E / S inter-processus. La raison en est que c'est le seul que vous pouvez facilement optimiser ici.

Maintenant, le problème est que son besoin en ressources est presque négligeable par rapport au besoin en ressources de la décompression. C'est pourquoi les optimisations ne feront vraiment rien de plus rapide.

Là où il pourrait être vraiment accéléré, ce serait un algorithme non-gzip modifié (c'est-à-dire la décompression), qui exclut la production réelle du flux de données décompressé; il calcule plutôt le nombre de sauts de ligne dans le flux décompressé à partir du flux compressé . Ce serait difficile, cela nécessiterait une connaissance approfondie de l'algorithme de gzip (une combinaison des algorithmes de compression LZW et Huffman ). Il est fort probable que l'algorithme ne permet pas d'optimiser significativement le temps de décompression avec l'éclair, qu'il suffit de connaître les décomptes de la nouvelle ligne. Même si cela était possible, une nouvelle bibliothèque de décompression gzip aurait dû être développée (elle n'existe pas jusqu'à ce que nous le sachions).

La réponse réaliste à votre question est que non, vous ne pouvez pas le faire beaucoup plus rapidement.

Vous pouvez peut-être utiliser une décompression gzip parallélisée, si elle existe. Il pourrait utiliser plusieurs cœurs de processeur pour la décompression. S'il n'existe pas, il pourrait être développé assez facilement.

Pour le xz , il existe un compresseur parallèle (pxz).

peterh - Réintégrer Monica
la source