Eu tenho um arquivo zip com tamanho de 1,5 GB.
Seu conteúdo é um arquivo de texto simples grande e ridículo (60 GB) e atualmente não tenho espaço suficiente em meu disco para extrair tudo nem quero extrair tudo, mesmo que tivesse.
Quanto ao meu caso de uso, seria suficiente se eu pudesse inspecionar partes do conteúdo.
Portanto, desejo descompactar o arquivo como um fluxo e acessar um intervalo do arquivo (como se pode fazer cabeça e cauda em um arquivo de texto normal).
Tanto pela memória (por exemplo, extraia no máximo 100kb a partir da marca de 32GB) ou por linhas (me dê as linhas de texto simples 3700-3900).
Existe uma maneira de conseguir isso?
Observe que
gzip
pode extrairzip
arquivos (pelo menos a primeira entrada nozip
arquivo). Portanto, se houver apenas um arquivo enorme nesse arquivo, você poderá fazer:Para extrair as 20 linhas começando com a 3000, por exemplo.
Ou:
Para o mesmo com bytes (assumindo uma
head
implementação que suporta-c
).Para qualquer membro arbitrário no arquivo, de forma Unixy:
Com o
head
embutido deksh93
(como quando/opt/ast/bin
está à frente em$PATH
), você também pode fazer:Observe que em qualquer caso
gzip
/bsdtar
/unzip
sempre será necessário descompactar (e descartar aqui) toda a seção do arquivo que leva à parte que você deseja extrair. Isso depende de como o algoritmo de compactação funciona.Uma solução usando unzip -p e dd, por exemplo, para extrair 10kb com deslocamento de 1000 blocos:
Nota: não tentei isso com dados realmente grandes ...
Se você tem controle sobre a criação desse grande arquivo zip, por que não considerar o uso de uma combinação de
gzip
ezless
?Isso permitiria que você usasse
zless
como um pager e visualizasse o conteúdo do arquivo sem ter que se preocupar com a extração.Se você não puder alterar o formato de compactação, isso obviamente não funcionará. Se assim for, sinto que
zless
é bastante conveniente.Para visualizar linhas específicas do arquivo, canalize a saída para o editor de fluxo do Unix, sed . Isso pode processar fluxos de dados arbitrariamente grandes, então você pode até mesmo usá-lo para alterar os dados. Para visualizar as linhas 3700-3900 conforme solicitado, execute o seguinte.
Eu me perguntei se era possível fazer algo mais eficiente do que descompactar desde o início do arquivo até o ponto. Parece que a resposta é não. No entanto, em algumas CPUs (Skylake)
zcat | tail
não acelera a CPU até a velocidade máxima do clock. Veja abaixo. Um decodificador personalizado pode evitar esse problema e salvar as chamadas do sistema de gravação de canal e talvez ser ~ 10% mais rápido. (Ou ~ 60% mais rápido no Skylake se você não ajustar as configurações de gerenciamento de energia).O melhor que você poderia fazer com um zlib personalizado com uma
skipbytes
função seria analisar os símbolos em um bloco de compactação para chegar ao fim sem fazer o trabalho de realmente reconstruir o bloco descompactado. Isso pode ser significativamente mais rápido (provavelmente pelo menos 2x) do que chamar a função de decodificação regular do zlib para sobrescrever o mesmo buffer e avançar no arquivo. Mas não sei se alguém escreveu essa função. (E acho que isso realmente não funciona, a menos que o arquivo tenha sido escrito especialmente para permitir que o decodificador reinicie em um determinado bloco).Eu esperava que houvesse uma maneira de pular os blocos Deflate sem decodificá-los, porque seria muito mais rápido. A árvore Huffman é enviada no início de cada bloco, para que você possa decodificar desde o início de qualquer bloco (eu acho). Ah, acho que o estado do decodificador é mais do que a árvore Huffman, é também os 32kiB anteriores de dados decodificados, e isso não é redefinido / esquecido nos limites do bloco por padrão. Os mesmos bytes podem continuar sendo referenciados repetidamente, portanto, podem aparecer apenas uma vez literalmente em um arquivo compactado gigante. (por exemplo, em um arquivo de log, o nome do host provavelmente permanece "quente" no dicionário de compactação o tempo todo e cada instância dele faz referência ao anterior, não ao primeiro).
O
zlib
manual diz que você deve usarZ_FULL_FLUSH
ao chamardeflate
se quiser que o fluxo compactado seja pesquisável até esse ponto. Ele "redefine o estado de compactação", então acho que sem isso, as referências reversas podem ir para o (s) bloco (s) anterior (is). Portanto, a menos que seu arquivo zip tenha sido escrito com blocos full-flush ocasionais (como cada 1G ou algo assim teria um impacto insignificante na compactação), acho que você teria que fazer mais trabalho de decodificação até o ponto desejado do que inicialmente pensamento. Eu acho que você provavelmente não pode começar no início de qualquer bloco.O resto disso foi escrito enquanto eu pensava que seria possível apenas encontrar o início do bloco contendo o primeiro byte que você deseja e decodificar a partir daí.
Mas, infelizmente, o início de um bloco Deflate não indica quanto tempo ele dura , para blocos compactados. Os dados incompressíveis podem ser codificados com um tipo de bloco não compactado que tem um tamanho de 16 bits em bytes na frente, mas os blocos compactados não: RFC 1951 descreve o formato de forma bastante legível . Os blocos com codificação Huffman dinâmica têm a árvore na frente do bloco (para que o descompactador não precise procurar no fluxo), portanto, o compressor deve ter mantido todo o bloco (compactado) na memória antes de escrevê-lo.
A distância máxima de referência para trás é de apenas 32kiB, portanto o compressor não precisa manter muitos dados não compactados na memória, mas isso não limita o tamanho do bloco. Os blocos podem ter vários megabytes de comprimento. (Isso é grande o suficiente para que a busca em disco valha a pena mesmo em uma unidade magnética, versus leitura sequencial na memória e apenas pulando dados na RAM, se fosse possível encontrar o final do bloco atual sem analisá-lo).
zlib torna os blocos tão longos quanto possível: De acordo com Marc Adler , o zlib só inicia um novo bloco quando o buffer de símbolos é preenchido, que com a configuração padrão é de 16.383 símbolos (literais ou correspondências)
Compactei a saída de
seq
(que é extremamente redundante e, portanto, provavelmente não é um ótimo teste), maspv < /tmp/seq1G.gz | gzip -d | tail -c $((1024*1024*1000)) | wc -c
roda a apenas ~ 62 MiB/s de dados compactados em um Skylake i7-6700k a 3,9 GHz, com RAM DDR4-2666. São 246 MiB/s de dados descompactados, o que é uma mudança grosseira em comparação com amemcpy
velocidade de aproximadamente 12 GiB/s para tamanhos de bloco muito grandes para caber no cache.(Com
energy_performance_preference
o padrão definido embalance_power
vez debalance_performance
, o controlador interno da CPU do Skylake decide executar apenas a 2,7 GHz, ~ 43 MiB / s de dados compactados. Eu usosudo sh -c 'for i in /sys/devices/system/cpu/cpufreq/policy[0-9]*/energy_performance_preference;do echo balance_performance > "$i";done'
para ajustá-lo. Provavelmente, essas chamadas de sistema frequentes não parecem reais vinculadas à CPU funciona para a unidade de gerenciamento de energia.)TL:DR:
zcat | tail -c
é vinculado à CPU mesmo em uma CPU rápida, a menos que você tenha discos muito lentos. O gzip usou 100% da CPU em que foi executado (e executou 1,81 instruções por clock, de acordo comperf
) etail
usou 0,162 da CPU em que foi executado (0,58 IPC). Caso contrário, o sistema estava ocioso.Estou usando o Linux 4.14.11-1-ARCH, que tem o KPTI ativado por padrão para contornar o Meltdown, então todas essas
write
chamadas de sistemagzip
são mais caras do que costumavam ser :/Ter o seek embutido em
unzip
ou (mas ainda usando a função de decodificaçãozcat
regularzlib
) salvaria todas as gravações de pipe e faria com que as CPUs Skylake rodassem na velocidade máxima do clock. (Esse downclock para alguns tipos de carga é exclusivo do Intel Skylake e posteriores, que descarregaram a tomada de decisão de frequência da CPU do sistema operacional, porque eles têm mais dados sobre o que a CPU está fazendo e podem aumentar / diminuir mais rapidamente. Isso é normalmente bom, mas aqui faz com que o Skylake não atinja a velocidade máxima com uma configuração de regulador mais conservadora).Nenhuma chamada do sistema, apenas reescrever um buffer que caiba no cache L2 até você atingir a posição inicial do byte desejada, provavelmente faria pelo menos alguns % de diferença. Talvez até 10%, mas estou apenas inventando números aqui. Não criei
zlib
um perfil detalhado para ver o tamanho da pegada de cache que ele tem e quanto a liberação de TLB (e, portanto, liberação de cache uop) em cada chamada do sistema dói com o KPTI ativado.Existem alguns projetos de software que adicionam um índice de busca ao formato de arquivo gzip . Isso não ajuda se você não conseguir que ninguém gere arquivos compactados pesquisáveis para você, mas outros leitores futuros podem se beneficiar.
Presumivelmente, nenhum desses projetos possui uma função de decodificação que saiba como pular um fluxo Deflate sem um índice, porque eles foram projetados para funcionar apenas quando um índice estiver disponível.
Você pode abrir o arquivo zip em uma sessão python, usando
zf = zipfile.ZipFile(filename, 'r', allowZip64=True)
e uma vez aberto você pode abrir, para leitura, qualquer arquivo dentro do arquivo zip e ler linhas, etc., dele como se fosse um arquivo normal.