Como posso otimizar este comando do Unix?

4

O comando a seguir leva cerca de 10 minutos para gerar o resultado

find . -name "muc*_*_20160920_*.unl*" | xargs zcat |
    awk -F "|" '{if($14=="20160920100643" && $22=="567094398953") print $0}'| head

Como posso melhorar seu desempenho?

    
por yemmy 22.09.2016 / 09:33

4 respostas

-1

Como observado nos comentários, zgrep é a melhor opção para esse tipo de tarefa com a opção globstar , que permite usar ** como all path inside the directory except hidden

shopt -s globstar
zgrep -m 10 '^\([^|]*|\)\{13\}20160920100643|\([^|]*|\)\{7\}567094398953' ./**muc*_*_20160920_*.unl*
shopt -u globstar
    
por 22.09.2016 / 10:25
4

Isso já está bastante otimizado. É difícil saber o que é o gargalo da garrafa sem saber mais detalhes como:

  • tipo de armazenamento (HD, SSD, rede, RAID)
  • número e tamanho médio dos arquivos correspondentes
  • número de diretórios e outros arquivos não correspondentes
  • número de campos em cada linha
  • comprimento médio de uma linha

Coisas que você pode fazer em qualquer caso:

  • substitua -print | xargs por -exec cmd {} + ou -print0 | xargs -r0 se seu find / xargs oferecer suporte a ele. -print | xargs não é apenas errado, mas também mais caro, pois xargs precisa decodificar caracteres para descobrir quais são espaços em branco e fazer um processamento de cotação caro.
  • corrija a localidade para C ( export LC_ALL=C ). Já que todos os caracteres envolvidos aqui ( | e dígitos decimais para o conteúdo do arquivo e letras latinas, ponto e sublinhado para os nomes dos arquivos) são parte do charset portátil, se seu charset é UTF-8 ou algum outro byte múltiplo charset, a mudança para o C com seu conjunto de caracteres de byte único garantirá muito trabalho para find e awk .
  • simplifique a parte awk para: awk -F "|" '$14 == "20160920100643" && $22 == "567094398953"' .
  • Como você está direcionando a saída para head , talvez queira desabilitar o buffer de saída para awk , de modo que ele exiba essas 10 linhas o mais cedo possível. Com gawk ou mawk , você pode usar fflush() para isso. Ou você pode adicionar um if (++n == 10) exit em awk .

Para resumir:

(export LC_ALL=C
find . -name "muc*_*_20160920_*.unl*" -exec zcat {} + |
  awk -F "|" '$14 == "20160920100643" && $22 == "567094398953" {
    print; if (++n == 10) exit}')

Se a CPU é o gargalo, em um sistema GNU multi-core, você pode tentar:

(export LC_ALL=C
find . -name "muc*_*_20160920_*.unl*" -print0 |
  xargs -r0P 4 -n 100 sh -c '
    zcat "$@" | 
      awk -F "|" "\ == "20160920100643" && \ == "567094398953" {
        print; fflush()}"' sh | head)

Para executar 4 zcat | awk jobs em paralelo em 100 lotes de arquivos.

Se esse 20160920100643 for um carimbo de data / hora, talvez você queira excluir arquivos que foram modificados pela última vez antes disso. Com GNU ou BSD find , adicione um -newermt '2016-09-20 10:06:42' .

Se as linhas tiverem um grande número de campos, você receberá uma penalidade por awk dividindo-a e alocando tantos campos $n . Usando uma abordagem que considera apenas os primeiros 22 campos pode acelerar as coisas:

grep -E '^([^|]*\|){13}20160920100643(\|[^|]*){7}\|567094398953(\||$)'

em vez do comando awk . Com GNU grep , adicione a opção --line-buffered para produzir as linhas o mais cedo possível na abordagem paralela ou -m 10 para parar após 10 correspondências na não-paralela.

Para resumir, se CPU é o gargalo e você tem pelo menos 4 núcleos de CPU em seu sistema e há pelo menos 400 arquivos muc * e você está em um sistema GNU (onde grep é geralmente significativamente mais rápido do que o GNU awk ):

(export LC_ALL=C
find . -name "muc*_*_20160920_*.unl*" -newermt '2016-09-20 10:06:42' -print0 |
  xargs -r0P 4 -n 100 sh -c '
    zcat "$@" | 
      grep --line-buffered -E \
        "^([^|]*\|){13}20160920100643(\|[^|]*){7}\|567094398953(\||$)"
  ' sh | head)

Observe que na abordagem paralela, você pode obter a saída dos comandos grep inter-mesclados (embora com linhas de buffer de linha e linhas fornecidas menores que alguns kilobytes, os limites de linha devem ser preservados).

    
por 22.09.2016 / 12:39
0

@ A resposta de Stéphane Chazelas fornece muitos detalhes sobre como você pode otimizar o pipeline de comando

find . -name "muc*_*_20160920_*.unl*" | xargs zcat |
    awk -F "|" '{if($14=="20160920100643" && $22=="567094398953") print $0}'| head

Vou fornecer outra maneira de abordar o problema em que você realmente mede onde está gastando mais tempo. Depois de descobrir onde o tempo é gasto, você pode determinar o que fazer a respeito. Se você quer melhorar seu tempo de execução de 10 minutos, otimizar um passo que leva 2 segundos é quase inútil.

Quando olho para o pipeline de comando, três coisas chamam minha atenção:

  1. find . - Como é a estrutura de diretórios? Quantos arquivos por diretório? O diretório é local para o sistema no qual o comando está sendo executado? Um sistema de arquivos remoto será muito mais lento.
  2. -name "muc*_*_20160920_*.unl*" - Quão perto estão todos nomes de arquivos na estrutura de diretórios? Eles estão todos "próximos" do nome e difícil / CPU intensivo para combinar? Porque todos os arquivos a árvore de diretórios tem que ter seu nome lido do disco e comparado para o padrão.
  3. xargs zcat - O xargs não parece ser um problema de desempenho, especialmente em comparação com os problemas de find acima e com o zcat em si. Mesmo se forem 10.000 ou 10.000.000 nomes de arquivos, o tempo usado para passar e analisar apenas os nomes é quase certamente insignificante comparado ao tempo gasto encontrando os nomes e então abrindo e descompactando todos os arquivos. Quão grandes são os arquivos? Porque você está descomprimindo o todos os arquivos todos que correspondem ao nome do arquivo find padrão.

Como você pode determinar qual é o maior problema de desempenho? Meça o desempenho de cada comando no pipeline. (Consulte o link para obter detalhes sobre o tempo de duração de todo o pipeline.) execute os seguintes comandos e veja quanto tempo cada etapa contribui para o tempo de processamento de todo o pipeline:

/usr/bin/time find . - Isso informa quanto tempo leva para percorrer sua árvore de diretórios. Se isso for lento, você precisa de um sistema de armazenamento melhor. Descarregue seu cache do sistema de arquivos [s ] antes de sincronizar isso para obter uma medição de pior caso, execute o find cronometrado novamente e veja quanto o armazenamento em cache afeta o desempenho. E se o diretório não for local, tente executar o comando no sistema real em que os arquivos estão.

/usr/bin/time find . -name "muc*_*_20160920_*.unl*" - Isso informará quanto tempo leva para combinar os nomes dos arquivos. Novamente, libere o cache do sistema de arquivos [s] e execute-o duas vezes.

/usr/bin/time bash -c "find . -name 'muc*_*_20160920_*.unl*' | xargs zcat > /dev/null" - Este é o que eu suspeito ser o principal componente do longo tempo de execução do seu pipeline. Se este for o problema, paralelizar os comandos zcat por resposta de Stéphane Chazelas pode ser a melhor resposta.

Continue adicionando etapas do pipeline de comando original ao que está sendo testado até descobrir onde você está gastando a maior parte do tempo. Novamente, suspeito que seja a etapa zcat . Se assim for, talvez a zcat de paralelização que @ Stéphane Chazelas postou ajudará.

Paralelizar zcat pode não ajudar - pode até prejudicar o desempenho e diminuir o processamento. Com apenas um zcat sendo executado por vez, o IO pode estar em um bom padrão de fluxo que minimiza as pesquisas de disco. Com vários processos de zcat sendo executados de uma só vez, as operações de E / S podem competir e realmente retardar o processamento, pois as cabeças de disco precisam procurar e qualquer leitura antecipada feita se torna menos eficaz.

Se a etapa zcat for o seu principal gargalo de desempenho e a execução de vários processos zcat de uma só vez não ajudar ou realmente atrapalhar você, seu pipeline será vinculado a E / S e você precisará resolver o problema usando armazenamento mais rápido.

E novamente - se o diretório não for local na máquina em que você está executando o pipeline de comando, tente executá-lo na máquina em que o sistema de arquivos realmente está.

    
por 23.09.2016 / 15:12
-1

Como apontado, não é possível dar a resposta correta sem alguns detalhes extras.

locate -0 -b -r '^muc.*_.*_20160920_.*.unl.*gz' | 
   xargs -0  zcat |
   awk -F "|" '$14=="20160920100643" && $22=="567094398953"'| head
  • 1: o local (se disponível) é muito mais rápido que ** ou find ; A expressão regular usada deve ser ajustada ...

  • 2 e 3: o filtro do pedido

Como @rudimeier, sabiamente apontou, há problemas com relação à disponibilidade e estado de atualização de locate . (por exemplo, na maioria das máquinas Linux, a localização é atualizada diariamente; dessa forma, não conseguirá encontrar arquivos criados hoje)

No entanto, se a localização estiver disponível, isso produzirá uma aceleração muito impressionante.

Seria interessante se o PO pudesse fornecer o time ... das várias soluções

    
por 22.09.2016 / 11:42