Existe uma maneira de tornar este one-liner mais rápido?

7

Contexto

Eu tenho um diretório de milhares de arquivos zip datados no formato YYYYMMDD_hhmmss.zip e cada um com cerca de 300K. Dentro de cada arquivo zip há cerca de 400 arquivos xml cada um com cerca de 3K.

O problema

Eu preciso pesquisar e encontrar uma determinada string dentro de um intervalo de datas dos arquivos zip.

A atual (embora medíocre) solução

Eu tenho o seguinte one-liner

find /home/mydir/ -type f | sort | \
awk "/xml_20140207_000016.zip/,/xml_20140207_235938.zip/" | \
xargs -n 1 -P 10 zipgrep "my search string"

O objetivo é

  1. lista todos os arquivos no meu diretório de mil arquivos
  2. classifique esta lista de arquivos
  3. recupera um intervalo de arquivos com base em datas fornecidas (esse comando awk imprime apenas linhas depois da primeira correspondência e até a segunda string correspondente)
  4. passa cada linha do resultado que corresponde a um único arquivo para zipgrep

A questão

Este one-liner roda terrivelmente devagar, mesmo com 10 processos em uma máquina de 24 núcleos. Eu acredito que é lento por causa do comando zipgrep , mas eu não sou inteligente o suficiente para saber como melhorá-lo. Eu não sei se deveria, mas estou um pouco envergonhado por um colega ter escrito uma ferramenta java que roda mais rápido que esse script. Eu gostaria de reverter isso, se possível. Então, alguém sabe como tornar este comando mais rápido neste contexto? Ou para melhorar qualquer parte dela?

    
por fifosine 07.07.2014 / 15:06

3 respostas

7

Há uma parte que você pode melhorar facilmente, mas não é a parte mais lenta.

find /home/mydir/ -type f | sort | \
awk "/xml_20140207_000016.zip/,/xml_20140207_235938.zip/"

Isso é um pouco desnecessário, pois primeiro lista todos os arquivos, depois classifica os nomes dos arquivos e extrai os mais interessantes. O comando find deve ser executado até a conclusão antes que a classificação possa começar.

Seria mais rápido listar apenas os arquivos interessantes em primeiro lugar, ou pelo menos um superconjunto tão pequeno quanto possível. Se você precisar de um filtro mais refinado em nomes do que o find é capaz, canalize para o awk, mas não classifique: o awk e outros filtros linha por linha podem processar linhas uma por uma, mas o tipo precisa da entrada completa.

find /home/mydir/ -name 'xml_20140207_??????.zip' -type f | \
awk 'match($0, /_[0-9]*.zip$/) &&
     (time = substr($0, RSTART+1, RLENGTH-5)) &&
     time >= 16 && time <= 235938' |
xargs -n 1 -P 10 zipgrep "my search string"

A parte que é mais obviamente sub-ótima é zipgrep. Aqui não há uma maneira fácil de melhorar o desempenho devido às limitações da programação da shell. O script zipgrep opera listando os nomes dos arquivos no arquivo e chamando grep no conteúdo de cada arquivo, um por um. Isso significa que o arquivo zip é analisado novamente para cada arquivo. Um programa em Java (ou Perl, ou Python, ou Ruby, etc.) pode evitar isso processando o arquivo apenas uma vez.

Se você quiser manter a programação shell, pode tentar montar cada zip em vez de usar o zipgrep.

… | xargs -n1 -P2 sh -c '
    mkdir "mnt$$-$1";
    fuse-zip "$1" "mnt$$-$1";
    grep -R "$0" "mnt$$-$1"
    fusermount -u "mnt$$-$1"
' "my search string"

Note que o paralelismo não vai te ajudar muito: o fator limitante na maioria das configurações será a largura de banda de E / S de disco, não o tempo de CPU.

Eu não fiz o comparativo de nada, mas acho que o melhor lugar para melhorar seria usar uma implementação do zipgrep em uma linguagem mais poderosa.

    
por 08.07.2014 / 03:14
6

Algumas ideias rápidas;

  • Se todos os arquivos estiverem em um único diretório, você poderá se livrar do find
  • Sua convenção de nomes de arquivos é ordenada por data, portanto você não precisa do sort bit
  • Com essas duas partes fora do caminho, e se o intervalo de datas for conhecido, você pode usar um nome de arquivo simples glob em vez de awk. Por exemplo (supondo que seu shell é bash ):

    • Todos os arquivos de um único dia

      echo xml_20140207_*.zip | xargs -n 1 -P 10 zipgrep "my search string"

    • Arquivos criados entre as 15:00 e as 18:00, em 07 de fevereiro ou 10 de fevereiro de 2014:

      echo xml_201402{07,10}_1{5..7}*.zip | xargs -n 1 -P 10 zipgrep "my search string"

por 07.07.2014 / 15:21
3

Não está claro onde está o seu gargalo. Vamos supor que seja na leitura dos arquivos. Dependendo do seu sistema de armazenamento, é mais rápido ler todo o arquivo antes de processá-lo. Isso é especialmente verdadeiro para zipgrep , que faz algumas pesquisas no arquivo: Se o arquivo não estiver completamente na memória, você aguardará o disco procurar.

find ... | parallel -j1 'cat {} >/dev/null; echo {}' | parallel zipgrep "my search string"

O arquivo acima irá cat um arquivo de cada vez e, portanto, será colocado no cache de memória, então execute um zipgrep por CPU, que então lerá do cache de memória.

Eu usei sistemas RAID onde você conseguiu uma velocidade de 6x lendo 10 arquivos em paralelo do que lendo um arquivo de cada vez ou lendo 30 arquivos em paralelo. Se eu tivesse que executar o acima no sistema RAID, eu ajustaria -j1 para -j10 .

Ao usar o GNU Parallel em vez de xargs você se protege da mistura de saída (veja link ).

    
por 04.11.2014 / 00:06