É traço ou algum outro shell "mais rápido" que o bash?

51

Sempre achei que o único benefício de usar o 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 traçar na esperança de que eles corressem mais rápido, e eu também achei isso no artigo DashAsBinSh no Wiki do Ubuntu:

The major reason to switch the default shell was efficiency. bash is an excellent full-featured shell appropriate for interactive use; indeed, it is still the default login shell. However, it is rather large and slow to start up and operate by comparison with dash.

Hoje em dia tenho usado muitos scripts bash para muitas coisas no meu sistema, e meu problema é que eu tenho um script em particular que estou executando continuamente 24 horas por dia, 7 dias por semana. computador 10 ° C mais do que no uso normal.

É um script bastante grande com muitos bashisms, então portá-los para POSIX ou algum outro shell seria muito demorado (e o POSIX realmente não importa para uso pessoal), mas valeria a pena se eu pudesse reduzir parte desse uso da CPU. Eu sei que há outras coisas a serem consideradas, como chamar um binário externo como sed para um bashum simples como ${foo/bar} , ou grep ao invés de =~ .

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

    
por Teresa e Junior 02.08.2014 / 15:17

4 respostas

35

SHELL SEQ:

Provavelmente, um meio útil de avaliar o desempenho de um shell é fazer várias avaliações simples e pequenas repetitivamente. É importante, penso eu, não apenas fazer um loop, mas fazer um loop por entrada , porque um shell precisa ler <&0 .

Eu pensei que isso complementaria os testes @cuonglm já postados porque demonstra o desempenho de um único processo de shell quando invocado , ao contrário do que demonstra a rapidez com que um processo 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 pequena otimização, se puder, incrementa 50 vezes por leitura de nova linha. Toda vez que a variável é incrementada, ela é impressa em stdout . Ele se comporta muito como uma espécie de seq cross nl .

E só para deixar bem claro o que ele faz - aqui está uma saída set -x; truncada depois de inseri-la antes de time na 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'

Assim, cada shell é chamado pela primeira vez como:

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

... para gerar a entrada que precisará fazer um loop quando ler em 3<<\SCRIPT - ou quando cat , de qualquer forma. E no outro lado desse |pipe ele se chama novamente como:

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

Então, além da chamada inicial para env (porque cat é realmente chamado na linha anterior) ; nenhum outro processo é chamado a partir do momento em que é chamado até sair. Pelo menos, espero que seja verdade.

Antes dos números ...

Eu deveria fazer algumas anotações sobre portabilidade.

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

  • mksh não possui printf embutido na maioria dos casos. Testes anteriores estavam muito atrasados - invocava /usr/bin/printf para cada corrida. Daí o echo -n acima.

  • talvez mais do que eu me lembro ...

De qualquer forma, 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 todos 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

ARBITRARY = TALVEZ OK?

Ainda assim, esse é 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á.

EDITAR por Teresa e Junior : @mikeserv e eu fiz muitos outros testes (veja nosso chat para detalhes), e descobrimos que os resultados podem ser resumidos assim:

  • Se você precisa de velocidade, vá definitivamente com o traço , é muito mais rápido do que qualquer outro shell e cerca de 4x mais rápido que o bash .
  • Embora o shell do busybox possa ser muito mais lento que traço , em alguns testes ele pode ser mais rápido, porque possui muitos de seus próprios utilitários, como grep , sed , sort , etc., que não possuem tantos recursos quanto os utilitários GNU mais utilizados, mas podem realizar o trabalho tanto quanto.
  • 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 à menor mksh , que é muito mais rápida que bash , e também tem alguns recursos exclusivos, como 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.
por 03.08.2014 / 04:40
17

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 só inicia um shell e não faz nada com o operador não operacional - cólon , então saia.

Como o resultado mostra, dash é extremamente mais rápido que bash na inicialização. dash é menor e depende de menos biblioteca compartilhada do 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)

Trata-se de tempo de inicialização, que tal operar? Deixe fazer outro benchmark:

$ 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 , dash ainda muito mais rápido que bash .

    
por 02.08.2014 / 20:00
6

Aqui estão alguns tempos de inicialização de vários shells em um UNIX certificado (Mac OS X 10.10.3). Eu reescrevi o teste para usar o tcsh para controlar os loops para que o shell sendo testado não fosse o que controlava os 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 claro, mas há um perdedor definitivo. De qualquer forma, o bash 4 é claramente mais lento que o bash 3. O Dash funciona bem, mas dado que o ksh93 agora é open-source, não há nenhuma razão real para usá-lo para tudo (desculpas se eu não entender nada): ksh93 é rápido, sólido e um padrão de fato em UNIX-land (se não em GNU / Linux-land); ele fornece um superconjunto da funcionalidade do shell POSIX (até onde eu entendo, o shell POSIX foi baseado em ksh88); é igual a bash como um shell interativo, embora atrasado comparado ao tcsh. E o perdedor é claro 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
    
por 01.06.2015 / 03:26
-1

Existem muitos casos de teste injustos em muitas respostas aqui. Se testar dois shells, use a sintaxe correta para cada um deles. E nos doublebraksets bash são muito mais rápidos e mais confiáveis do que os singlebrackets, então há uma diferença de velocidade muito menor. Use também otimistas bashisms e, em seguida, essas diferenças de velocidade são mais menos também. No meu sistema, o bash corre como o inferno, com uso pesado de bashisms. E equivalentes posix no traço são mais lentos aqui. Não está 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 traço sempre pode ser o mais rápido. A meu ver, posix está muito desatualizado. E em termos de compatibilidade, é realmente difícil encontrar sistemas relevantes hoje em dia, eles não usam um shell bash.

Uma boa comparação é: para 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 mostram o desempenho real dos concorrentes. Eu vejo no meu trabalho diário, cujo shell é mais rápido em muitos casos de uso.

    
por 10.06.2017 / 12:38