web-dev-qa-db-fra.com

Dash ou un autre Shell est-il "plus rapide" que bash?

J'ai toujours pensé que le seul avantage de l'utilisation de dash au lieu de bash était que le dash était plus petit, et donc de nombreuses instances de dash démarreraient plus rapidement au démarrage.

Mais j'ai fait quelques recherches, et j'ai trouvé certaines personnes migrant tous leurs scripts pour se lancer dans l'espoir qu'elles s'exécuteraient plus rapidement, et je l'ai également trouvé dans l'article DashAsBinSh dans le wiki Ubuntu:

La principale raison de changer le Shell par défaut était efficacité. bash est un excellent shell complet, adapté à une utilisation interactive; en effet, il s'agit toujours du shell de connexion par défaut. Cependant, il est assez grand et lent à démarrer et opérer par rapport au tiret.

De nos jours, j'utilise beaucoup de scripts bash pour beaucoup de choses sur mon système, et mon problème est que j'ai un script particulier que j'exécute en continu 24h/24 et 7j/7, qui génère environ 200 enfants, qui ensemble chauffent mon ordinateur à 10 ° C plus qu'en utilisation normale.

C'est un script assez volumineux avec beaucoup de bashismes, donc les porter sur POSIX ou un autre Shell prendrait beaucoup de temps (et POSIX n'a ​​pas vraiment d'importance pour un usage personnel), mais cela vaut la peine si je pouvais réduire une partie de cela L'utilisation du processeur. Je sais qu'il y a aussi d'autres choses à considérer, comme appeler un binaire externe comme sed pour un bashisme simple comme ${foo/bar} ou grep au lieu de =~.

TL; DR est vraiment plus lent à démarrer et opérer en comparaison avec le tiret? Y a-t-il d'autres shells Unix qui sont plus efficaces que bash?

57
Teresa e Junior

Shell SEQ:

Un moyen utile de comparer les performances d'un shell consiste probablement à effectuer de nombreuses évaluations très petites et simples de manière répétitive. Il est important, je pense, non seulement de boucler, mais de boucler sur entrée, car un Shell doit lire <&0.

Je pensais que cela compléterait les tests @ cuonglm déjà publié car cela démontre les performances d'un seul processus Shell une fois invoqué, contrairement à la sienne qui montre la vitesse à laquelle un processus Shell se charge lorsqu'il est invoqué. De cette façon, entre nous, nous couvrons les deux côtés de la médaille.

Voici une fonction pour faciliter la démo:

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

Il incrémente une variable une fois par lecture de nouvelle ligne ou, comme légère optimisation, s'il le peut, il incrémente 50 fois par lecture de nouvelle ligne. Chaque fois que la variable est incrémentée, elle est imprimée dans stdout. Il se comporte un peu comme une sorte de seq cross nl.

Et juste pour clarifier ce qu'il fait - voici une sortie tronquée set -x; Après l'avoir insérée juste avant time dans la fonction ci-dessus:

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

Donc, chaque Shell est d'abord appelé comme:

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

... pour générer l'entrée qu'il devra boucler lors de la lecture dans 3<<\SCRIPT - ou quand cat le fera, de toute façon. Et de l'autre côté de cela |pipe Il s'appelle à nouveau comme:

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

Donc, à part l'appel initial à env (parce que cat est en fait appelé dans la ligne précédente); aucun autre processus n'est appelé à partir du moment où il est appelé jusqu'à sa sortie. Du moins, j'espère que c'est vrai.

Avant les chiffres ...

Je devrais prendre quelques notes sur la portabilité.

  • posh n'aime pas $((n=n+1)) et insiste sur $((n=$n+1))

  • mksh n'a pas de printf intégré dans la plupart des cas. Les tests précédents avaient beaucoup de retard - il invoquait /usr/bin/printf Pour chaque exécution. D'où le echo -n Ci-dessus.

  • peut-être plus que je m'en souvienne ...

Quoi qu'il en soit, aux chiffres:

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

Ça les mettra d'un coup ...

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

ARBITRAIRE = PEUT-ÊTRE OK?

Pourtant, c'est un test plutôt arbitraire, mais il teste l'entrée de lecture, l'évaluation arithmétique et l'expansion variable. Peut-être pas complet, mais peut-être proche de là.

EDIT par Teresa e Junior : @mikeserv et moi avons fait de nombreux autres tests (voir notre chat pour plus de détails), et nous ont trouvé que les résultats pouvaient être résumés comme suit:

  • Si vous avez besoin de vitesse, optez certainement pour tiret , il est beaucoup plus rapide que tout autre Shell et environ 4x plus rapide que bash =.
  • Alors que le shell de busybox peut être beaucoup plus lent que dash, dans certains tests, il pourrait être plus rapide, car il a beaucoup de ses propres utilitaires utilisateur, comme grep, sed, sort, etc., qui n'ont pas autant de fonctionnalités que le GNU utilitaires, mais peut faire le travail autant.
  • Si la vitesse n'est pas tout ce qui vous importe, ksh (ou ksh93) peut être considéré comme le meilleur compromis entre vitesse et traits. Sa vitesse est comparable à celle du plus petit mksh, qui est bien plus rapide que bash, et il possède également des fonctionnalités uniques, comme arithmétique à virgule flottante .
  • Bien que bash soit célèbre pour sa simplicité, sa stabilité et sa fonctionnalité, il a été le plus lent de tous les obus dans la majorité de nos tests, et par une large marge .
39
mikeserv

Faisons une référence.

Avec 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

Avec 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

Chaque itération démarre uniquement un Shell et ne fait rien avec l'opérateur no-op - deux points , puis quitte.

Comme le montre le résultat, dash est extrêmement plus rapide que bash au démarrage. dash est plus petit et dépend d'une bibliothèque moins partagée 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)

Il s'agit du temps de démarrage, que diriez-vous de fonctionner. Faisons une autre référence:

$ 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

Avec un simple test 1 = 1, dash toujours beaucoup plus rapide que bash.

20
cuonglm

Voici quelques timings de démarrage de divers shells dans un UNIX certifié (Mac OS X 10.10.3). J'ai réécrit le test pour utiliser tcsh pour contrôler les boucles afin que le Shell testé ne soit pas celui qui contrôle les boucles. Pour chaque shell, la boucle est exécutée cinq fois avant le chronométrage, pour garantir que l'exécutable du shell et les scripts sont dans le cache.

Comme vous pouvez le voir, il n'y a pas de gagnant clair, mais il y a un perdant définitif. Quoi qu'il en soit, bash 4 est nettement plus lent que bash 3. Dash fonctionne bien, mais étant donné que ksh93 est maintenant open-source, il n'y a aucune vraie raison de ne pas l'utiliser pour tout (excuses si je ne comprends pas les subtilités de licence): ksh93 est rapide, solide , et un standard de facto en UNIX-land (sinon en GNU/Linux-land); il fournit un sur-ensemble de la fonctionnalité du shell POSIX (pour autant que je sache, le shell POSIX était basé sur ksh88); il est égal à bash en tant que shell interactif, bien qu'en retard par rapport à tcsh. Et le perdant est bien sûr 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
7
Alun Carr

Il y a trop de cas de test injustes dans de nombreuses réponses ici. Si vous testez deux shells, utilisez la syntaxe correcte pour chacun d'eux. Et dans bash les doubles supports sont beaucoup plus rapides et plus fiables que les simples supports, il y a donc une différence de vitesse beaucoup moins importante. Utilisez également des bashismes optimisés et ces différences de vitesse sont également plus moindres. Sur mon système, bash fonctionne comme un enfer, avec une utilisation intensive des bashismes. Et les équivalents posix dans le tiret sont plus lents ici. Ce n'est pas vrai que le tiret est toujours plusieurs fois plus rapide que bash. Vraiment, il est assez injuste de comparer les lignes de commande posix dans les deux, qui peut toujours être le plus rapide. À mon avis, le posix est très dépassé. Et en termes de compatibilité, il est vraiment difficile de trouver des systèmes pertinents de nos jours, ils n'utilisaient pas de shell bash.

Une bonne comparaison est la suivante: utiliser la meilleure ligne de commande possible dans chaque shell, pour terminer un travail spécifique. Non seulement exactement la même ligne de commande, alors qu'un seul Shell a vraiment un avantage ici. Des comparaisons comme celle-ci ne sont pas fiables et ne montrent pas les performances réelles des concurrents. Je vois dans mon travail quotidien, que Shell est plus rapide dans de nombreux cas d'utilisation.

Par exemple, pour remplacer tous les caractères a de la chaîne par des caractères b, en bash vous pouvez écrire "${varname//a/b}" Tandis que dans le tiret vous devez appeler un outil externe comme ceci: "$(echo "$varname" | sed 's/a/b/g')". Si vous devez le répéter plusieurs centaines de fois - l'utilisation du bashisme peut vous donner une accélération 2x.

0
jeff

Tous les problèmes de performances ne sont pas des problèmes d'efficacité, seulement la majorité d'entre eux le sont, contrairement aux problèmes naïfs raisonnablement efficaces qui nécessitent des solutions algorithmiques. Si nous devions réparer l'un ou l'autre en totalité, la solution la plus efficace a plus souvent que la meilleure performance.

Personne ne peut vous dire comment corriger le problème, mais il existe un problème d'efficacité spécifique que POSIX Shell peut résoudre, c'est la raison pour laquelle tous les scripts de démarrage ont été portés de Bash vers Dash.

Si vous rencontrez des problèmes de performances en raison d'un grand nombre de scripts démarrant en même temps, mais qu'ils sont tous assez raisonnablement efficaces chacun d'eux-mêmes, alors tous les porter sur POSIX Shell est une solution préférable.

Cependant, je vérifierais probablement d'abord que vous devez réellement générer autant de processus à la fois et que la partie spécifique du script ne peut pas être réécrite différemment. Vous ne mentionnez pas si les 200 processus enfants générés sont d'autres scripts, il pourrait être possible de les porter simplement au lieu du plus grand script parent.

0
J. M. Becker