Gostaria de saber se era possível fazer algo mais eficiente do que descomprimir 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 aumenta a CPU até a velocidade máxima do relógio. Ver 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 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 substituir 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 isso 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 de Huffman, também são os 32 kiB 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 literalmente apenas uma vez 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 todas as instâncias referenciam o anterior, não o primeiro).
O zlib
manual diz que você deve usar Z_FULL_FLUSH
ao ligar deflate
se quiser que o fluxo compactado seja procurado até esse ponto. Ele "redefine o estado de compactação", então acho que sem isso, as referências anteriores podem ir para o (s) bloco (s) anterior (es). Portanto, a menos que seu arquivo zip tenha sido gravado com blocos ocasionais de liberação completa (como todo 1G ou algo teria um impacto insignificante na compactação), acho que você teria que fazer mais do trabalho de decodificação até o ponto desejado do que eu estava inicialmente pensando. 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 encontrar o início do bloco que contém o primeiro byte desejado e decodificar a partir daí.
Infelizmente, porém, o início de um bloco Deflate não indica quanto tempo é , para blocos compactados. Os dados incompatíveis podem ser codificados com um tipo de bloco não compactado que possui um tamanho de 16 bits em bytes na frente, mas os blocos compactados não: O RFC 1951 descreve o formato com bastante facilidade . Blocos com codificação dinâmica de Huffman têm a árvore na frente do bloco (para que o descompressor não precise procurar no fluxo), portanto o compressor deve manter todo o bloco (comprimido) na memória antes de escrevê-lo.
A distância máxima de referência para trás é de apenas 32 kB, 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. (Isso é grande o suficiente para que o disco busque valer a pena, mesmo em uma unidade magnética, vs. leitura seqüencial na memória e apenas pular dados na RAM, se for possível encontrar o final do bloco atual sem analisá-lo).
O zlib cria blocos o maior tempo possível:
De acordo com Marc Adler , o zlib só inicia um novo bloco quando o buffer do símbolo é preenchido, que com a configuração padrão é 16.383 símbolos (literais ou correspondências)
Gzipei a saída de seq
(que é extremamente redundante e, portanto, provavelmente não é um ótimo teste), mas pv < /tmp/seq1G.gz | gzip -d | tail -c $((1024*1024*1000)) | wc -c
é executado com apenas 62 MiB / s de dados compactados em um Skylake i7-6700k a 3.9GHz, com DDR4-2666 RAM. São 246MiB / s de dados descomprimidos, que são trocas de chump em comparação com a memcpy
velocidade de ~ 12 GiB / s para tamanhos de bloco grandes demais para caber no cache.
(Com energy_performance_preference
o padrão definido em balance_power
vez de balance_performance
, o governador interno da CPU do Skylake decide rodar apenas a 2,7 GHz, ~ 43 MiB / s de dados compactados. Eu uso sudo sh -c 'for i in /sys/devices/system/cpu/cpufreq/policy[0-9]*/energy_performance_preference;do echo balance_performance > "$i";done'
para ajustá-los. Provavelmente, essas chamadas frequentes do sistema não parecem reais vinculadas à CPU trabalho 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 na qual executou (e executou 1,81 instruções por relógio, de acordo com perf
), e tail
usou 0,162 da CPU na qual executou (0,58 IPC). Caso contrário, o sistema estava praticamente inativo.
Estou usando o Linux 4.14.11-1-ARCH, que tem o KPTI ativado por padrão para solucionar o Meltdown, portanto, todas as write
chamadas de sistema gzip
são mais caras do que costumavam ser: /
Ter a busca incorporada unzip
ou zcat
(mas ainda usando a zlib
função de decodificação regular ) salvaria todas essas gravações de pipe e faria com que as CPUs Skylake funcionassem a toda velocidade. (Esse downclock para alguns tipos de carga é exclusivo do Intel Skylake e posterior, que descarregou a tomada de decisão da frequência da CPU do SO, porque eles têm mais dados sobre o que a CPU está fazendo e podem aumentar / diminuir mais rapidamente. normalmente bom, mas aqui a Skylake não aumenta sua velocidade máxima com uma configuração de governador mais conservadora).
Nenhuma chamada do sistema, apenas reescrever um buffer que se encaixa no cache L2 até você alcançar a posição de byte inicial desejada, provavelmente faria pelo menos uma diferença de alguns%. Talvez até 10%, mas estou inventando números aqui. Não detalhei zlib
detalhadamente o tamanho de um espaço ocupado em cache e o quanto a liberação TLB (e, portanto, a liberação de cache de cache) em todas as chamadas do sistema prejudica 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 alguém gere arquivos compactados procuráveis para você, mas outros futuros leitores podem se beneficiar.
Presumivelmente, nenhum desses projetos tem uma função de decodificação que sabe pular um fluxo Deflate sem um índice, porque eles são projetados para funcionar apenas quando um índice está disponível.