A maneira mais rápida e eficiente de obter um número de registros (linhas) em um arquivo compactado com gzip

16

Estou tentando fazer uma contagem de registros em um arquivo gzip de 7,6 GB. Encontrei poucas abordagens usando o zcatcomando

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

Isso funciona, mas leva muito tempo (mais de 10 minutos para obter a contagem). Eu tentei mais algumas abordagens como

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

Todos esses três comandos estão executando muito rapidamente, mas fornecendo uma contagem incorreta de 28173811.

Como posso executar uma contagem de registros em um período mínimo de tempo?

Rahul
fonte
5
Por que você precisa contar o número de registros? Se você estiver tentando contá-los antes de processá-los, isso significa que você precisará descompactar o arquivo duas vezes.
Andrew Henle
3
Mais informações sobre por que você está fazendo isso seriam úteis. Se houver algo em andamento - ou seja, você comprime regularmente um monte de arquivos e, posteriormente, precisa saber o número de registros - por que não contá-los como estão compactados e incorporar o número ao nome do arquivo?
Jamesqf
3
A leitura de um arquivo de 9,7 GB de um disco mecânico é inerentemente mais lenta. Armazene o arquivo em um SSD e veja quanto mais rápido o gunzip / zcat é executado. Mas como o @jamesqf diz, armazene a contagem de linha no nome do arquivo ou em um arquivo no tgz, e extrair esse arquivo será muito mais rápido.
ChuckCottrill
2
Existem boas razões teóricas para você não poder evitar esse trabalho. Um formato de compressão que permite determinar alguma propriedade útil dos dados "sem descomprimir que" é muito bonito, por definição, não tão bom um formato de compressão como poderia ser :)
Hobbs

Respostas:

28

Os comandos sed, perle awkque você mencionou podem estar corretos, mas todos lêem os dados compactados e contam caracteres de nova linha. Esses caracteres de nova linha não têm nada a ver com os caracteres de nova linha nos dados descompactados.

Para contar o número de linhas nos dados não compactados, não há como contorná-los. Sua abordagem com zcaté a abordagem correta e desde que os dados é tão grande, ele vai ter tempo para descomprimir.

A maioria dos utilitários que lida com gzipcompactação e descompactação provavelmente usará as mesmas rotinas de biblioteca compartilhada para fazer isso. A única maneira de acelerar isso seria encontrar uma implementação das zlibrotinas que sejam de algum modo mais rápidas que as padrão e reconstruir, por exemplo, zcatpara usá-las.

Kusalananda
fonte
11
Seria um exercício de programação não trivial, mas factível. O ponto principal é não reconstruir zcat. Uma parte significativa do trabalho de zcatestá gerando a saída real. Mas se você está apenas contando \ncaracteres, isso não é necessário. gzipa compactação funciona basicamente substituindo longas cadeias comuns por outras mais curtas. Portanto, você só precisa se preocupar com as longas sequências no dicionário que contêm \nae contar a ocorrência (ponderada) delas. Por exemplo, devido às regras em inglês, .\né uma string comum de 16 bits.
MSalters
19

Use unpigz.

A resposta de Kusalananda está correta, você vai precisar para descompactar o arquivo inteiro para digitalizar seu conteúdo. /bin/gunzipfaz isso o mais rápido possível, em um único núcleo. Pigz é uma implementação paralela gzipque pode usar múltiplos núcleos.

Infelizmente, o próprio descompressão de arquivos gzip normais não pode ser paralelizado, mas pigznão oferecem uma versão melhorada gunzip, unpigzque faz trabalhos relacionados, tais como a leitura, escrita e checksumming em um segmento separado. Em alguns benchmarks rápidos, unpigzé quase o dobro da velocidade gunzipda minha máquina core i5.

Instale pigzcom seu gerenciador de pacotes favorito e use em unpigzvez de gunzipou em unpigz -cvez de zcat. Portanto, seu comando se torna:

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

Tudo isso pressupõe que o gargalo é a CPU, não o disco, é claro.

marcelm
fonte
4
Minha pigzpágina de manual afirma que a descompressão não pode ser paralelizada, pelo menos não sem fluxos de esvaziamento especialmente preparados para esse fim. Como resultado, o pigz usa um único thread (o thread principal) para descompressão, mas criará outros três threads para leitura, gravação e verificação de cálculos, o que pode acelerar a descompressão em algumas circunstâncias . Ainda assim, como se eu acho que é pelo menos duas vezes mais rápido do que gzip, se não por causa do paralelismo
Stéphane Chazelas
@ StéphaneChazelas Bom ponto! Isso explica a aceleração levemente decepcionante da descompressão. Eu editei minha postagem para refletir melhor essas informações.
Marcelm
5

O problema com todos os pipelines é que você está basicamente dobrando o trabalho. Não importa a rapidez da descompressão, os dados ainda precisam ser transferidos para outro processo.

O Perl possui o PerlIO :: gzip, que permite ler diretamente os fluxos compactados em gzip. Portanto, ele pode oferecer uma vantagem, mesmo que sua velocidade de descompressão não corresponda à 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]': $!";

Eu tentei com um arquivo compactado em gzip de 13 MB (descompacta para 1,4 GB) em um antigo MacBook Pro 2010 com 16 GB de RAM e um ThinkPad T400 antigo com 8 GB de RAM com o arquivo já no cache. No Mac, o script Perl era significativamente mais rápido do que o uso de pipelines (5 segundos vs 22 segundos), mas no ArchLinux, ele perdia a desvantagem:

$ time -p ./gzlc.pl spy.gz 
1154737
real 4,49
usuário 4.47
sys 0,01

versus

$ time -p unpigz -c spy.gz | wc -l
1154737
real 3,68
usuário 4.10
sys 1,46

e

$ time -p zcat spy.gz | wc -l
1154737
real 6,41
usuário 6.08
sys 0.86

Claramente, o uso unpigz -c file.gz | wc -lé o vencedor aqui, tanto em termos de velocidade. E essa linha de comando simples certamente supera a escrita de um programa, por mais curto que seja.

Sinan Ünür
fonte
11
Eu acho que você está superestimando os recursos necessários para mover os dados entre dois processos, em comparação com os cálculos de descompressão. Tente comparar as várias abordagens;)
marcelm 8/17/17
2
@ SinanÜnür No meu sistema Linux x86_64 (também hardware antigo) gzip | wctem a mesma velocidade que o seu script perl. E pigz | wcé o dobro da velocidade. gziproda com a mesma velocidade, independentemente de eu gravar a saída em / dev / null ou canalizar o wcque eu acredito é que a "biblioteca gzip" usada pelo perl é mais rápida que a ferramenta de linha de comando gzip. Talvez haja outro problema específico do Mac / Darwin com os pipes. Ainda é incrível que esta versão perl seja competitiva.
Rudimeier
11
Na minha instalação do Linux x86_64, parece que está melhor zcate pior que unpigz. Estou impressionado com a rapidez com que o pipeline é no sistema Linux comparado ao Mac. Eu não esperava isso, mesmo que eu já tenha observado o mesmo programa ser executado mais rapidamente em uma VM Linux Linux com CPU limitada no mesmo Mac do que no bare metal.
Sinan Ünür
11
Isso é interessante; no meu sistema (Debian 8.8 amd64, quad core i5), o script perl é um pouco mais lento ... O arquivo .gz de 109M zcat | wc -l. Honestamente, estou impressionado com a variação que as pessoas estão relatando aqui, especialmente entre Linux e MacOS X!
Marcelm
Não sei se posso generalizar o que estou vendo no meu Mac, algo estranho está acontecendo. Com o arquivo de 1,4 GB descompactado, wc -lleva 2,5 segundos. gzcat compressed.gz > /dev/nullleva 2,7 segundos. No entanto, o pipeline leva 22 segundos. Se eu tentar o GNU wc, leva apenas meio segundo no arquivo descomprimido, mas 22 segundos no pipeline. O GNU zcatleva o dobro do tempo para executar zcat compressed.gz > /dev/null. Este é o Mavericks, antigo CPU Core 2 Duo, 16 GB de RAM, SSD Crucial MX100.
Sinan Ünür
4

A resposta de Kusalananda é principalmente correta. Para contar linhas, você precisa procurar novas linhas. No entanto, é teoricamente possível procurar novas linhas sem descompactar completamente o arquivo.

O gzip usa a compactação DEFLATE. DEFLATE é uma combinação de codificação LZ77 e Huffman. Pode haver uma maneira de descobrir apenas o nó do símbolo Huffman para nova linha e ignorar o resto. Quase certamente existe uma maneira de procurar novas linhas codificadas usando o L277, manter uma contagem de bytes e ignorar todo o resto.

Então, IMHO é teoricamente possível encontrar uma solução mais eficiente que unpigz ou zgrep. Dito isto, certamente não é prático (a menos que alguém já tenha feito isso).

IAmBarry
fonte
7
Um grande problema com essa idéia é que os símbolos de Huffman usados ​​pelo DEFLATE correspondem a sequências de bits após a compactação LZ77, portanto, pode não haver uma relação simples entre eles e os caracteres U + 000A no arquivo descompactado. Por exemplo, talvez um símbolo de Huffman signifique os últimos cinco bits de "." seguido pelos três primeiros bits de "\ n" e outro símbolo significa os últimos cinco bits de "\ n" seguidos pelos oito bits de "T".
zwol
@zwol Não, a parte LZ77 do algoritmo Deflate comprime sequências de bytes, não sequências de bits. en.wikipedia.org/wiki/DEFLATE#Duplicate_string_elimination
Ross Ridge
11
@RossRidge Huh, eu não sabia disso, mas não acho que isso invalide o que eu disse. Os símbolos de Huffman podem, me parece, com base no próximo parágrafo dessa referência, cada um expandir para um número variável de bits, eles não precisam produzir um número inteiro de bytes.
Zwol
11
@zwol Claro, você precisa procurar por sequências de bits de código Huffman correspondentes no fluxo de bits, mas esta resposta não sugere o contrário. O problema com esta resposta é que determinar quais códigos de Huffman acabam gerando ou mais caracteres de nova linha não é simples. Os códigos LZ77 que geram novas linhas mudam constantemente à medida que a janela deslizante se move, o que significa que os códigos Huffman também estão mudando. Você precisaria implementar todo o algoritmo de descompressão, exceto a parte de saída, e talvez alguma parte da janela deslizante, pois você só está interessado nas novas linhas.
Ross Ridge
1

Pode ser feito usando zgrepcom -cflag e $parâmetro.

Nesse caso, -c instrua o comando a gerar o número de linhas correspondentes e o regex $ corresponde ao final da linha, de forma que corresponda a todas as linhas ou arquivos.

zgrep -c $ T.csv.gz 

Como comentado por @ StéphaneChazelas - zgrepsó é um script em torno zcate grepe deve proporcionar um desempenho semelhante ao sugestão original dezcat | wc -l

Yaron
fonte
2
Oi Yaron obrigado pela resposta até mesmo o zgrep está tomando tanto tempo como zcat eu preciso encontrar alguma outra abordagem que eu acho
Rahul
8
zgrepgeralmente é um script que invoca zcat(o mesmo que gzip -dcq) para descompactar os dados e alimentá-los grep, por isso não vai ajudar.
Stéphane Chazelas
11
@ StéphaneChazelas - obrigado pelo comentário, atualize minha resposta para refleti-lo.
Yaron
0

Como você pode ver, a maioria das respostas tenta otimizar o que pode: o número de alternâncias de contexto e E / S entre processos. O motivo é que esse é o único que você pode otimizar aqui facilmente.

Agora, o problema é que sua necessidade de recursos é quase insignificante para a necessidade de recursos da descompressão. É por isso que as otimizações realmente não tornam nada mais rápido.

Onde poderia ser realmente acelerado, seria um algoritmo un-gzip (isto é, descompressão) modificado, que deixa de fora a produção real do fluxo de dados descompactado; em vez disso, apenas calcula o número das novas linhas do fluxo descomprimido do comprimido um. Seria difícil, exigiria o conhecimento profundo do algoritmo do gzip (alguma combinação dos algoritmos de compressão LZW e Huffman ). É bem provável que o algoritmo não permita otimizar significativamente o tempo de descompressão com o raio, que precisamos apenas conhecer a contagem da nova linha. Mesmo que fosse possível, essencialmente uma nova biblioteca de descompactação gzip deveria ter sido desenvolvida (ela não existe até que seja conhecida).

A resposta realista para sua pergunta é que não, você não pode torná-la significativamente mais rápida.

Talvez você possa usar alguma descompressão paralela ao gzip, se existir. Ele pode usar vários núcleos da CPU para a descompressão. Se não existir, poderia ser desenvolvido com relativa facilidade.

Para o xz , existe um compressor paralelo (pxz).

peterh - Restabelecer Monica
fonte