O dash ou algum outro shell é "mais rápido" que o bash?

57

Eu sempre pensei que o único benefício do uso do traço em vez do bash era que o traço era menor e, portanto, muitas instâncias do traço começariam mais rapidamente no momento da inicialização.

Mas eu fiz algumas pesquisas e encontrei algumas pessoas migrando todos os seus scripts para correr na esperança de que rodassem mais rapidamente, e também encontrei isso no artigo DashAsBinSh no Ubuntu Wiki:

O principal motivo para mudar o shell padrão foi a eficiência . o bash é um excelente shell completo adequado para uso interativo; de fato, ainda é o shell de login padrão. No entanto, é bastante grande e lento iniciar e operar em comparação com o traço.

Atualmente, eu tenho usado muitos scripts bash para muitas coisas no meu sistema, e meu problema é que eu tenho um script específico que estou executando continuamente 24/7, que gera cerca de 200 crianças, que juntas aquecem meu computador 10 ° C mais do que no uso normal.

É um script bastante grande, com muitos basismos, portanto, portá-los para o POSIX ou algum outro shell consumiria muito tempo (e o POSIX realmente não importa para uso pessoal), mas valeria a pena se eu pudesse reduzir um pouco disso. Utilização do CPU. Eu sei que também há outras coisas a considerar, como chamar um binário externo como sedum simples basismo ${foo/bar}, ou em grepvez de =~.

TL; DR é realmente mais lento para iniciar e operar em comparação com o traço? Existem outros shells Unix que são mais eficientes que o bash?

Teresa e Junior
fonte
12
Se você deseja portá-lo para desempenho, você acha que seria melhor fazê-lo em alguma outra linguagem (perl, python, ruby)? Geralmente, eles são muito mais eficientes, embora isso dependa da natureza exata da tarefa.
precisa saber é o seguinte
Ponto menor: [também deve ser incorporado.
Mikel
2
Lembre-se de que, ao contrário do que você está preocupado com o uso da memória, a diferença mostra principalmente se você está fazendo cálculos no shell, e não em programas externos (ou seja, você está usando o shell da maneira errada!); Por exemplo, no meu computador, um script usando um loop while para contar até um milhão (sem fazer mais nada) é ~ 2x mais rápido em mksh / zsh e> 2x mais rápido em traços, mas em um script real eu descarregaria o máximo possível para outros programas.
Loreb
3
bashcostumava ser muito lento. Ele fez muito progresso recentemente, mas para a maioria das coisas, ainda é mais lento que a maioria das outras conchas.
Stéphane Chazelas
11
Não use bashism simples . [ "$foo" != "${foo#*bar}" ]lida com sua coisa grep. E a sedcoisa: while [ "$foo" != "${foo#*bar}" ]; do s=$s${foo%%bar*} foo=${foo#*bar} ; done ; foo=$s$foo. Você pode colocar qualquer coisa em uma função.
mikeserv

Respostas:

39

SHELL SEQ:

Provavelmente, um meio útil de comparar o desempenho de um shell é fazer várias avaliações muito pequenas e simples repetidamente. Acho que é importante não apenas fazer um loop, mas fazer um loop sobre a entrada , porque um shell precisa ler <&0.

Eu pensei que isso complementaria os testes que o @cuonglm já postou, porque demonstra o desempenho de um único processo de shell uma vez invocado, em oposição ao que demonstra a rapidez com que um processo de shell é carregado quando invocado. Desta forma, entre nós, cobrimos os dois lados da moeda.

Aqui está uma função para facilitar a demonstração:

sh_bench() (                                               #dont copy+paste comments
    o=-c sh=$(command -v "$1") ; shift                     #get shell $PATH; toss $1
    [ -z "${sh##*busybox}" ] && o='ash -c'                 #cause its weird
    set -- "$sh" $o "'$(cat <&3)'" -- "$@"                 #$@ = invoke $shell
    time env - "$sh" $o "while echo; do echo; done|$*"     #time (env - sh|sh) AC/DC
) 3<<-\SCRIPT                                                                      
#Everything from here down is run by the different shells    
    i="${2:-1}" l="${1:-100}" d="${3:-                     
}"; set -- "\$((n=\$n\${n:++\$i}))\$d"                     #prep loop; prep eval
    set -- $1$1$1$1$1$1$1$1$1$1                            #yup
    while read m                                           #iterate on input
    do  [ $(($i*50+${n:=-$i})) -gt "$(($l-$i))" ] ||       #eval ok?
            eval echo -n \""$1$1$1$1$1"\"                  #yay!
        [ $((n=$i+$n)) -gt "$(($l-$i))" ] &&               #end game?
            echo "$n" && exit                              #and EXIT
        echo -n "$n$d"                                     #damn - maybe next time
    done                                                   #done 
#END
SCRIPT                                                     #end heredoc

Ele incrementa uma variável uma vez por leitura de nova linha ou, como uma leve otimização, se puder, incrementa 50 vezes por leitura de nova linha. Sempre que a variável é incrementada, ela é impressa stdout. Ele se comporta muito como uma espécie de seqcruz nl.

E apenas para deixar bem claro o que faz - aqui está uma set -x;saída truncada depois de inseri-la antes timena função acima:

time env - /usr/bin/busybox ash -c '
     while echo; do echo; done |
     /usr/bin/busybox ash -c '"'$(
         cat <&3
     )'"' -- 20 5 busybox'

Portanto, cada shell é chamado primeiro como:

 env - $shell -c "while echo; do echo; done |..."

... para gerar a entrada que ele precisará fazer loop quando ler 3<<\SCRIPT- ou quando cat, de qualquer maneira. E por outro lado, |pipeele se chama novamente como:

"...| $shell -c '$(cat <<\SCRIPT)' -- $args"

Portanto, além da chamada inicial para env (porque catna verdade é chamada na linha anterior) ; nenhum outro processo é chamado desde o momento em que é chamado até sua saída. Pelo menos, espero que seja verdade.

Antes dos números ...

Eu deveria fazer algumas anotações sobre portabilidade.

  • poshnão gosta $((n=n+1))e insiste em$((n=$n+1))

  • mkshnão tem um printfbuiltin na maioria dos casos. Testes anteriores estavam muito atrasados ​​- eram invocadores /usr/bin/printfa cada execução. Daí o echo -nacima.

  • talvez mais do que me lembro ...

Enfim, para os números:

for sh in dash busybox posh ksh mksh zsh bash
do  sh_bench $sh 20 5 $sh 2>/dev/null
    sh_bench $sh 500000 | wc -l
echo ; done

Isso vai levá-los de uma só vez ...

0dash5dash10dash15dash20

real    0m0.909s
user    0m0.897s
sys     0m0.070s
500001

0busybox5busybox10busybox15busybox20

real    0m1.809s
user    0m1.787s
sys     0m0.107s
500001

0posh5posh10posh15posh20

real    0m2.010s
user    0m2.060s
sys     0m0.067s
500001

0ksh5ksh10ksh15ksh20

real    0m2.019s
user    0m1.970s
sys     0m0.047s
500001

0mksh5mksh10mksh15mksh20

real    0m2.287s
user    0m2.340s
sys     0m0.073s
500001

0zsh5zsh10zsh15zsh20

real    0m2.648s
user    0m2.223s
sys     0m0.423s
500001

0bash5bash10bash15bash20

real    0m3.966s
user    0m3.907s
sys     0m0.213s
500001

ARBITRÁRIO = TALVEZ OK?

Ainda assim, este é um teste bastante arbitrário, mas testa entrada de leitura, avaliação aritmética e expansão de variáveis. Talvez não seja abrangente, mas possivelmente perto de lá.

EDIT por Teresa e Junior : @mikeserv e eu fizemos muitos outros testes (veja nosso bate-papo para mais detalhes), e achamos que os resultados podem ser resumidos assim:

  • Se você precisar de velocidade, vá definitivamente com o traço , é muito mais rápido que qualquer outro shell e cerca de 4x mais rápido que o bash .
  • Enquanto busybox shell 's pode ser muito mais lento do que o traço , em alguns testes que poderia ser mais rápido, porque tem muitos de seus próprios utilitários userland, como grep, sed, sort, etc., que não tem tantos recursos como o GNU comumente usado utilitários, mas pode fazer o trabalho da mesma maneira.
  • Se a velocidade não é tudo o que você gosta, o ksh (ou ksh93 ) pode ser considerado o melhor compromisso entre velocidade e recursos. Sua velocidade se compara ao mksh menor , que é muito mais rápido que o bash , e também possui alguns recursos exclusivos, como a aritmética de ponto flutuante .
  • Embora o bash seja famoso por sua simplicidade, estabilidade e funcionalidade, foi o mais lento de todos os shells na maioria dos nossos testes e por uma grande margem.
mikeserv
fonte
Não consigo fazer com que esse código funcione no bash (e também no ksh e zsh), apenas no dash, mksh e pdksh. Bash Eu tentei 4.2.37(1)-releaseno Debian e 4.2.45(2)-releaseno Porteus LiveCD (Slackware). Sem null=, em vez de emitir números, ele funciona como se eu tivesse pressionado Return continuamente, então eu tenho que matar o bash com SIGKILL .
Teresa e Junior
E eu também tentei bash --posix, sem sucesso.
Teresa e Junior
@TeresaeJunior - que talvez seja possível - embora eu não acredite que funcione zsh. zshseqüestrará oe ttybem, ele lançará um shell interativo. Espero bashque faça o mesmo - e é por isso que tenho o cuidado de chamar apenas seu --posixlink. Posso fazer o que você espera da maioria deles, mas pode ser mais trabalhoso do que vale a pena. Você está ligando bashou está ligando sh?
mikeserv
@TeresaeJunior Você pode entrar aqui e postar a saída? Gostaria apenas de ter uma idéia melhor do que está acontecendo.
mikeserv
Não devo adicionar o texto da minha resposta na parte inferior da sua, para complementá-la e depois excluir a minha?
Teresa e Junior
20

Vamos fazer uma referência.

Com bash:

$ strace -cf bash -c 'for i in $(seq 1 1000); do bash -c ":"; done'

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 99.12    0.376044         188      2004      1002 wait4
  0.74    0.002805           3      1002           clone
  0.03    0.000130           0      4037           read
  0.03    0.000119           0     15026           rt_sigprocmask
  0.03    0.000096           0     15040      6017 stat
  0.01    0.000055           0      8011           open
  0.01    0.000024           0      5013           getegid
  0.01    0.000021           0     16027           rt_sigaction
  0.00    0.000017           0      9020      5008 access
  0.00    0.000014           0      1001      1001 getpeername
  0.00    0.000013           0      1001           getpgrp
  0.00    0.000012           0      5013           geteuid
  0.00    0.000011           0     15025           mmap
  0.00    0.000011           0      1002           rt_sigreturn
  0.00    0.000000           0         1           write
  0.00    0.000000           0      8017           close
  0.00    0.000000           0      7011           fstat
  0.00    0.000000           0      8012           mprotect
  0.00    0.000000           0      2004           munmap
  0.00    0.000000           0     18049           brk
  0.00    0.000000           0         1           pipe
  0.00    0.000000           0         1           dup2
  0.00    0.000000           0      1001           getpid
  0.00    0.000000           0      1002           execve
  0.00    0.000000           0      1001           uname
  0.00    0.000000           0      1001           getrlimit
  0.00    0.000000           0      5013           getuid
  0.00    0.000000           0      5013           getgid
  0.00    0.000000           0      1001           getppid
  0.00    0.000000           0      1002           arch_prctl
  0.00    0.000000           0      1001           time
------ ----------- ----------- --------- --------- ----------------
100.00    0.379372                158353     13028 total

Com dash:

$ strace -cf bash -c 'for i in $(seq 1 1000); do dash -c ":"; done'
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 73.88    0.008543           4      2004      1002 wait4
 25.35    0.002932           3      1002           clone
  0.62    0.000072           0      9026           rt_sigprocmask
  0.10    0.000011           0      1002           rt_sigreturn
  0.05    0.000006           0     15027           rt_sigaction
  0.00    0.000000           0      1037           read
  0.00    0.000000           0         1           write
  0.00    0.000000           0      2011           open
  0.00    0.000000           0      2017           close
  0.00    0.000000           0      2040        17 stat
  0.00    0.000000           0      2011           fstat
  0.00    0.000000           0      8025           mmap
  0.00    0.000000           0      3012           mprotect
  0.00    0.000000           0      1004           munmap
  0.00    0.000000           0      3049           brk
  0.00    0.000000           0      3020      3008 access
  0.00    0.000000           0         1           pipe
  0.00    0.000000           0         1           dup2
  0.00    0.000000           0      1001           getpid
  0.00    0.000000           0         1         1 getpeername
  0.00    0.000000           0      1002           execve
  0.00    0.000000           0         1           uname
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0        13           getuid
  0.00    0.000000           0        13           getgid
  0.00    0.000000           0      1013           geteuid
  0.00    0.000000           0        13           getegid
  0.00    0.000000           0      1001           getppid
  0.00    0.000000           0         1           getpgrp
  0.00    0.000000           0      1002           arch_prctl
  0.00    0.000000           0         1           time
------ ----------- ----------- --------- --------- ----------------
100.00    0.011564                 60353      4028 total

Cada iteração inicia apenas um shell e não faz nada com o operador não operacional - dois pontos e , em seguida, sai.

Como mostra o resultado, dashé extremamente mais rápido do que bashna inicialização. dashé menor e depende de menos biblioteca compartilhada que bash:

$ du -s /bin/bash 
956 /bin/bash

$ du -s /bin/dash 
108 /bin/dash

$ ldd /bin/bash
    linux-vdso.so.1 =>  (0x00007fffc7947000)
    libtinfo.so.5 => /lib/x86_64-linux-gnu/libtinfo.so.5 (0x00007f5a8110d000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f5a80f09000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5a80b7d000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f5a81352000)

$ ldd /bin/dash
    linux-vdso.so.1 =>  (0x00007fff56e5a000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb24844c000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fb2487f3000)

É sobre o tempo de inicialização, que tal operar. Vamos fazer outra referência:

$ time dash -c 'for i in $(seq 1 1000000);do [ 1 = 1 ];done'

real    0m2.684s
user    0m2.728s
sys     0m0.100s

$ time bash -c 'for i in $(seq 1 1000000);do [ 1 = 1 ];done'

real    0m6.996s
user    0m6.820s
sys     0m0.376s

Com teste simples 1 = 1, dashainda muito mais rápido que bash.

cuonglm
fonte
Sua resposta é muito apreciada, mas parece que você está medindo apenas a rapidez com que o shell é inicializado, e não a velocidade com que opera, certo?
Teresa e Junior
11
@TeresaeJunior: Sim, eu mencionei apenas o tempo de inicialização.
precisa saber é
Eu suponho que seq 1 100000deveria ser seq 1 1000?
Mikel
11
Mas para o seu dashcaso de teste é apenas seq 1 1000?
Mikel
Oh, desculpe, é 1000para inicialização e 1000000operação, fixo.
precisa saber é
7

Aqui estão alguns horários de inicialização de vários shells em um UNIX certificado (Mac OS X 10.10.3). Reescrevi o teste para usar o tcsh para controlar os loops, para que o shell que estava sendo testado não fosse o responsável pelo controle dos loops. Para cada shell, o loop é executado cinco vezes antes do tempo, para garantir que o shell executável e os scripts estejam em cache.

Como você pode ver, não há vencedor nítido, mas há um perdedor definitivo. De qualquer forma, o bash 4 é nitidamente mais lento que o bash 3. O Dash tem um bom desempenho, mas, como o ksh93 agora é de código aberto, não há motivo real para não usá-lo para tudo (desculpas se eu não entender as sutilezas do licenciamento): o ksh93 é rápido, sólido , e um padrão de fato na terra UNIX (se não na terra GNU / Linux); fornece um superconjunto da funcionalidade do shell POSIX (até onde eu entendi, o shell POSIX foi baseado no ksh88); é igual ao bash como um shell interativo, embora atrasado em comparação com o tcsh. E o perdedor é obviamente zsh.

/bin/bash is v3.2.57(1)
/usr/local/bin/bash is v4.3.33(1)
dash is v0.5.8
ksh is v93u+
mksh is vR50f
pdksh is v5.2.14
/opt/heirloom/5bin/sh is from SysV
yash is v2.37
zsh is v5.0.5

% cat driver.csh 
#!/bin/tcsh

foreach s ( $* )
    echo
    echo "$s"
    foreach i ( `seq 1 5` )
        ./simple_loop.csh "$s"
    end
    /usr/bin/time -p ./simple_loop.csh "$s"
end

% cat simple_loop.csh 
#!/bin/tcsh

set shell = `which ${1}`
foreach i ( `seq 1 1000` )
    ${shell} -c ":"
end

% ./driver.csh /bin/bash /usr/local/bin/bash dash ksh mksh pdksh /opt/heirloom/5bin/sh yash zsh 
/bin/bash
real         4.21
user         1.44
sys          1.94

/usr/local/bin/bash
real         5.45
user         1.44
sys          1.98

dash
real         3.28
user         0.85
sys          1.11

ksh
real         3.48
user         1.35
sys          1.68

mksh
real         3.38
user         0.94
sys          1.14

pdksh
real         3.56
user         0.96
sys          1.17

/opt/heirloom/5bin/sh
real         3.46
user         0.92
sys          1.11

yash
real         3.97
user         1.08
sys          1.44

zsh
real        10.88
user         3.02
sys          5.80
Alun Carr
fonte
Minha conclusão foi usar o ksh93 também. Está sob a Licença Pública Comum, que foi aprovada pela FSF.
Teresa e Junior
0

Existem muitos casos de teste injustos em muitas respostas aqui. Se testar duas conchas, use a sintaxe correta para cada uma delas. E no bash, os colchetes duplos são muito mais rápidos e confiáveis ​​do que os colchetes simples, portanto, há uma diferença de velocidade muito menor. Use também bashisms otimizados e, em seguida, essas diferenças de velocidade também serão menores. No meu sistema, o bash funciona como o inferno, com uso pesado de bashisms. E os equivalentes posix no painel são mais lentos aqui. Não é correto que o traço seja sempre várias vezes mais rápido que o bash. Realmente, é bastante injusto comparar as linhas de comando do posix em ambos, que podem sempre ser os mais rápidos. Na minha opinião, o posix está muito desatualizado. E em termos de compatibilidade, é realmente difícil encontrar sistemas relevantes hoje em dia, eles não usavam um shell bash.

Uma boa comparação é: usar a melhor linha de comando possível em cada shell, para concluir um trabalho específico. Não apenas exatamente a mesma linha de comando, quando apenas um shell realmente tem uma vantagem aqui. Comparações como essa não são confiáveis ​​e não mostraram o desempenho real dos concorrentes. Eu vejo no meu trabalho diário, qual shell é mais rápido em muitos casos de uso.

Por exemplo, para substituir todos os acaracteres na string com bcaracteres, em bash você pode escrever "${varname//a/b}"enquanto na corrida você tem que chamar ferramenta externa como esta: "$(echo "$varname" | sed 's/a/b/g')". Se você precisar repeti-lo algumas centenas de vezes - usar o basismo pode dar 2x velocidade.

jeff
fonte
3
Você tem algum exemplo com o qual você pode atualizar sua resposta para mostrar como o bash pode fechar a lacuna de desempenho ou até ser mais rápido nas tarefas equivalentes? Sua resposta seria muito mais forte com alguns exemplos específicos.
Eric Renouf