Script Bash; otimização da velocidade de processamento

10

Fiquei me perguntando se existem diretrizes gerais para otimizar scripts Bash.

  • Por exemplo, é mais conveniente escrever loops do que linhas de comandos, mas também é mais rápido processar o sistema? Exemplo:

    for i in a b c; do echo $i; done
    
    echo a
    echo b
    echo c
    
  • Às vezes, as pessoas apresentam soluções diferentes para o mesmo problema. Por exemplo, sed, cut, awk, e echosão todos capazes de tirar dígitos a partir de uma string. Eu queria saber se você pode dizer que quanto menos código de dígitos tiver, mais rápido será se você usar:

    1. o mesmo comando, por exemplo

      STRING=abc.def
      echo ${STRING} | sed 's/.def//g'
      echo ${STRING} | sed '$s/....$//'
      
    2. comandos diferentes, por exemplo

      STRING=abc.def
      echo ${STRING} | cut -d . -f 1
      echo ${STRING} | sed 's/.def//g'
      
Vincent
fonte
2
Na maioria dos casos, eu preferiria a legibilidade do que a velocidade.
Bernhard
1
a) Sim, existe uma sobrecarga no uso de um loop for, mas isso não importa. b) tente minimizar comandos externos e apenas substitua as coisas do bash. E se você precisa otimizar seu script de shell, está fazendo algo errado e deve considerar o uso de uma linguagem com melhor suporte ao criador de perfil.
Ulrich Dangel 6/03/2013
Expressões regulares geralmente são menos eficientes em termos de desempenho do que qualquer outra opção; portanto, se você encontrar uma maneira de fazer algo sem uma (por exemplo, cortar), use isso. Claro, isso pode afetar a legibilidade se a sua capacidade de leitura é limitada a expressões regulares;)
Goldilocks

Respostas:

8

Os shells não fazem nenhuma reorganização do código que recebem, é apenas interpretado uma linha após a outra (nada mais faz muito sentido em um interpretador de comando). Grande parte do tempo gasto pelo shell vai para análise / análise / lançamento lexical dos programas chamados.

Para operações simples (como as que mostram os exemplos no final da pergunta), eu ficaria surpreso se o tempo para carregar os programas não varresse diferenças minúsculas de velocidade.

A moral da história é que, se você realmente precisa de mais velocidade, é melhor usar uma linguagem (semi) compilada como Perl ou Python, que é mais rápida de executar no início, na qual é possível escrever muitas das operações mencionadas diretamente e não precisa chamar programas externos e tem a opção de chamar programas externos ou chamar módulos C (ou o que seja) otimizados para executar grande parte do trabalho. Essa é a razão pela qual no Fedora o "açúcar de administração do sistema" (GUIs, essencialmente) é escrito em Python: Pode adicionar uma GUI agradável sem muito esforço, rápido o suficiente para tais aplicativos, ter acesso direto às chamadas do sistema. Se a velocidade não for suficiente, pegue C ++ ou C.

Mas não vá lá, a menos que você possa provar que o ganho de desempenho vale a perda de flexibilidade e o tempo de desenvolvimento. Os scripts do shell não são tão ruins de ler, mas estremeço quando me lembro de alguns scripts usados ​​para instalar o Ultrix que tentei decifrar. Eu desisti, muita "otimização de script de shell" havia sido aplicada.

vonbrand
fonte
1
+1, mas muitas pessoas argumentam que é mais provável que haja um ganho em flexibilidade e tempo de desenvolvimento usando algo como python ou perl x shell, não uma perda. Eu diria que apenas use um script de shell se necessário, ou o que você está fazendo envolve uma quantidade abundante de comandos específicos do shell.
Goldilocks
22

A primeira regra da otimização é: não otimize . Teste primeiro. Se os testes mostrarem que seu programa está muito lento, procure possíveis otimizações.

A única maneira de ter certeza é fazer benchmark para o seu caso de uso. Existem algumas regras gerais, mas elas se aplicam apenas a volumes típicos de dados em aplicativos típicos.

Algumas regras gerais que podem ou não ser verdadeiras em qualquer circunstância específica:

  • Para processamento interno no shell, o ATT ksh é o mais rápido. Se você fizer muitas manipulações de strings, use ATT ksh. Dash vem em segundo; bash, pdksh e zsh ficam para trás.
  • Se você precisar chamar um shell com frequência para executar uma tarefa muito curta a cada vez, o traço vence devido ao seu baixo tempo de inicialização.
  • Iniciar um processo externo custa tempo, por isso é mais rápido ter um pipeline com peças complexas do que um pipeline em um loop.
  • echo $fooé mais lento que echo "$foo", porque sem aspas duplas, ele se divide $fooem palavras e interpreta cada palavra como um padrão curinga de nome de arquivo. Mais importante, esse comportamento de divisão e globbing raramente é desejado. Portanto, lembre-se de sempre colocar aspas duplas em torno de substituições de variáveis ​​e substituições de comandos: "$foo", "$(foo)".
  • As ferramentas dedicadas tendem a conquistar as ferramentas de uso geral. Por exemplo, ferramentas como cutou headpodem ser emuladas sed, mas sedserão mais lentas e mais awklentas. O processamento de cadeia de shell é lento, mas para cadeias curtas é melhor do que chamar um programa externo.
  • Linguagens mais avançadas, como Perl, Python e Ruby, muitas vezes permitem que você escreva algoritmos mais rápidos, mas eles têm um tempo de inicialização significativamente maior, portanto valem a pena pelo desempenho para grandes quantidades de dados.
  • No Linux, pelo menos, os pipes tendem a ser mais rápidos que os arquivos temporários.
  • A maioria dos usos de scripts de shell são relacionados a processos ligados a E / S, portanto, o consumo da CPU não importa.

É raro que o desempenho seja uma preocupação nos scripts de shell. A lista acima é meramente indicativa; é perfeitamente bom usar métodos "lentos" na maioria dos casos, pois a diferença costuma ser uma fração de um por cento.

Normalmente, o objetivo de um script de shell é fazer algo rapidamente. Você precisa ganhar muito com a otimização para justificar gastar minutos extras escrevendo o script.

Gilles 'SO- parar de ser mau'
fonte
2
Embora pythone rubydefinitivamente seja mais lento, pelo menos no meu sistema, perlé tão rápido quanto bashou ksh. O GNU awk é significativamente mais lento que o GNU sed, especialmente em locais utf-8, mas não é verdade para todos os awks e todos os seds. o ksh93> dash> pdksh> zsh> bash nem sempre é tão claro quanto isso. Algumas conchas são melhores em algumas coisas que outras, e o vencedor nem sempre é o mesmo.
Stéphane Chazelas
2
Re "você tem que ganhar muito com ..." : se "você" incluir a base de usuários, é verdade. Com scripts de shell em pacotes Linux populares, os usuários coletivamente perdem várias ordens de magnitude mais tempo do que o programador apressado economiza.
agc
2

Expandiremos aqui o exemplo de exemplo acima para ilustrar algumas características de desempenho do interpretador de script de shell. Comparando bashe dashintérpretes para este exemplo, onde um processo é gerado para cada um dos 30.000 arquivos, mostra que o dash pode bifurcar os wcprocessos quase duas vezes mais rápido quebash

bash-4.2$ time dash -c 'for i in *; do wc -l "$i"; done>/dev/null'
real    0m1.238s
user    0m0.309s
sys     0m0.815s


bash-4.2$ time bash -c 'for i in *; do wc -l "$i"; done>/dev/null'
real    0m1.422s
user    0m0.349s
sys     0m0.940s

Comparando a velocidade de loop base, não invocando os wcprocessos, mostra que o loop do traço é quase 6 vezes mais rápido!

$ time bash -c 'for i in *; do echo "$i">/dev/null; done'
real    0m1.715s
user    0m1.459s
sys     0m0.252s



$ time dash -c 'for i in *; do echo "$i">/dev/null; done'
real    0m0.375s
user    0m0.169s
sys     0m0.203s

O loop ainda é relativamente lento em qualquer shell, como demonstrado anteriormente, portanto, para escalabilidade, devemos tentar usar técnicas mais funcionais para que a iteração seja executada em processos compilados.

$ time find -type f -print0 | wc -l --files0-from=- | tail -n1
    30000 total
real    0m0.299s
user    0m0.072s
sys     0m0.221s

A descrição acima é, de longe, a solução mais eficiente e ilustra bem o ponto em que se deve fazer o mínimo possível no shell script e tem como objetivo apenas usá-lo para conectar a lógica existente disponível no rico conjunto de utilitários disponíveis em um sistema UNIX.

Roubado de erros comuns de script de shell por Pádraig Brady.

Rahul Patil
fonte
1
Uma regra genérica: o manuseio do descritor de arquivos também custa, portanto reduza sua contagem. Em vez de for i in *; do wc -l "$i">/dev/null; donefazer melhor for i in *; do wc -l "$i"; done>/dev/null.
Manatwork
@manatwork a saída será também nula de timecmd
Rahul Patil
@manatwork Good ... Agora, por favor também me dar saída sem invocar wc -l, verifique Eu atualizei na pós sua saída
Rahul Patil
Bem, as medidas anteriores foram feitas em um diretório menor. Agora eu criei uma com 30000 arquivos e repetiu os testes: pastebin.com/pCV6QKp2
manatwork
Esses benchmarks falham em permitir os diferentes horários de início de cada shell. Os benchmarks feitos dentro de cada shell seriam melhores.
agc 01/01