VAR = 'arquivo de cat' e, em seguida, repetir o eco “$ VAR” é mais lento do que repetir o arquivo cat. Por quê?

3

Existem aproximadamente 10.000 arquivos em files/ e 10.000 linhas em metadata.csv . Onde metadata.csv contém informações sobre os arquivos. Eu tenho um script de shell que imprime informações sobre cada arquivo e, em seguida, um conteúdo do arquivo:

#!/bin/sh
for FILE in 'find files/ -type f'
do
    ID='echo $FILE | sed 's/some/thing/''
    cat metadata.csv | awk -v ORS="" -v id=$ID -F "\t" '$1==id {
        print "id=\""id"\" Some=\""$2"\" Thing=\""$5"\" "}'
    cat $FILE
done

Eu achei que poderia agilizar isso atribuindo conteúdo de metadata.csv a uma variável METADATA . Eu pensei que não iria ler o arquivo do disco de cada vez, mas iria armazená-lo na memória:

#!/bin/sh
METADATA='cat metadata.csv'
for FILE in 'find files/ -type f'
do
    ID='echo $FILE | sed 's/some/thing/''
    echo "$METADATA" | awk -v ORS="" -v id=$ID -F "\t" '$1==id {
        print "id=\""id"\" Some=\""$2"\" Thing=\""$5"\" "}'
    cat $FILE
done

Mas o segundo não é mais rápido. A primeira roda cerca de 1 minuto e a segunda dura mais de 2 minutos.

Como funciona e porque o segundo script é mais lento, não mais rápido?

editar : no meu sistema / bin / sh - > traço

    
por mejem 10.03.2016 / 00:06

1 resposta

2

Você não forneceu informações suficientes para que outros reproduzam seu índice de referência. Eu fiz o meu próprio e encontrei o método echo para ser um pouco mais rápido com dash e ksh, e sobre o mesmo com mksh. A proporção foi muito inferior a 1: 2, mesmo quando houve uma diferença. Obviamente, isso depende de muitas coisas, incluindo o shell, o kernel, a implementação dos utilitários e o conteúdo dos arquivos de dados.

Entre esses dois métodos, não há um vencedor óbvio. A leitura do disco custa praticamente nada porque o arquivo estará no cache. Chamar cat tem a sobrecarga de bifurcação de um processo externo, enquanto echo é um shell bultin. Se seu sh for bash, seu echo builtin imprimirá seu argumento uma linha por vez, mesmo quando a saída estiver indo para um pipe, o que pode representar um pouco da lentidão. Dash e ksh não fazem isso; normalmente eles têm melhor desempenho que o bash.

Existem várias otimizações que você pode fazer no seu script.

  • Uma otimização óbvia no método cat é usar o redirecionamento ( <metadata.csv awk … ) ou passar metadata.csv como um argumento para o awk. Nos meus testes, o redirecionamento foi muito ligeiramente mais rápido do que echo e não houve uma diferença mensurável entre o redirecionamento e awk … metadata.csv .

  • Quando você usa uma expansão de variável sem aspas, além de falha horrivelmente se o valor contiver certos caracteres , faz um trabalho extra para o shell, porque ele tem que fazer a divisão e globbing. Sempre use aspas duplas em torno de substituições de variáveis, a menos que você saiba por que precisa omiti-las.

  • Da mesma forma, você está analisando a saída de find , que irá se afogar em alguns nomes de arquivos e requer trabalho extra. A solução canônica é usar find -exec ; isso pode ou não ser mais rápido, porque isso também tem que fazer um trabalho extra para iniciar um shell para processar os arquivos.
  • Eu presumo que o seu script awk é simplificado a partir da coisa real. Com o script que você mostra, assumindo que a primeira coluna do arquivo CSV contém apenas caracteres que não são especiais em expressões regulares, você pode tentar usar o sed; seria mais enigmático, mas poderia ser um pouco mais rápido porque as ferramentas mais especializadas geralmente são mais rápidas. Não há garantias de que você obterá uma melhoria, muito menos uma mensurável.
  • Quando você define ID , chama um programa externo. Dependendo exatamente do que você está fazendo aqui, isso pode ser feito com as próprias construções de manipulação de strings do shell: elas normalmente não são muito rápidas e não muito poderosas, mas não requerem a chamada de um programa externo.

Ao todo, combinando essas otimizações locais, eu escolheria

#!/bin/ksh
find files/ -type f -exec sh -c '
  for FILE do
    ID=${FILE//some/thing}
    sed '/^$ID\t/ s/\([^\t]*\)\t\([^\t]*\)\t[^\t]*\t[^\t]*\t\([^\t]*\).*/id="" Some="" Thing=""/' metadata.csv
    cat "$FILE"
  done' _ {} +

Pode haver um algoritmo mais rápido. Você está processando todo o conjunto de metadados para cada arquivo. Especialmente se cada arquivo corresponder apenas a uma linha, isso é um monte de comparações desnecessárias. É provável que seja mais rápido gerar a lista de IDs de nomes de arquivos e agrupá-los com os metadados. Código não testado:

#!/bin/ksh
join -j 1 -t $'\t' -o 2.1,2.2,2.5,1.2 \
     <(find files/ -type f | sed 's!/some$!/thing\t&!' | sort) \
     <(sort metadata.csv) |
awk -F '\t' '{
    print "id =\"" $1 "\" Some=\"" $2 "\" Thing=\" $3 "\"";
    system("cat 7" $4 "7"); # Assuming no single quotes in file names
}'
    
por 10.03.2016 / 02:48