Dash ou un autre shell est-il «plus rapide» que bash?

57

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

Cependant, j'ai effectué des recherches et découvert que certaines personnes effectuaient la migration de tous leurs scripts dans l'espoir qu'elles fonctionneraient plus vite. J'ai également trouvé cela dans l'article DashAsBinSh dans le wiki d'Ubuntu:

La principale raison de changer de shell par défaut était l' efficacité . bash est un excellent shell complet convenant à une utilisation interactive; En effet, c'est toujours le shell de connexion par défaut. Cependant, son démarrage et son fonctionnement sont plutôt volumineux et lents par rapport à dash.

De nos jours, j'utilise beaucoup de scripts bash pour beaucoup de choses sur mon système, et le problème est que j'ai un script particulier que je lance en permanence, 24 heures sur 24, 7 jours sur 7, qui engendre environ 200 enfants et qui réchauffe mon ordinateur 10 ° C plus qu'en usage normal.

C'est un script assez volumineux avec beaucoup de bashismes, donc le portage sur POSIX ou sur un autre shell prendrait beaucoup de temps (et POSIX n'a ​​pas vraiment d'importance pour un usage personnel), mais il serait utile que je réduise un peu cette tâche. L'utilisation du processeur. Je sais qu'il y a aussi d'autres choses à considérer, comme appeler un binaire externe comme sedpour un simple bashisme comme ${foo/bar}, ou à la grepplace de =~.

TL; DR est vraiment plus lent à démarrer et à fonctionner par rapport à dash? Existe-t-il d'autres shell Unix plus efficaces que bash?

Teresa e Junior
la source
12
Si vous allez le porter pour la performance, pensez-vous qu'il serait préférable de le faire entièrement dans un autre langage (perl, python, ruby)? Je pense qu’elles sont généralement beaucoup plus efficaces, bien que cela dépende de la nature exacte de la tâche.
goldilocks
Point mineur: [devrait également être un intégré.
Mikel
2
Gardez à l'esprit que, contrairement à ce qui vous préoccupe au sujet de l'utilisation de la mémoire, la différence montre principalement si vous effectuez des calculs dans le shell plutôt que dans des programmes externes (c'est-à-dire que vous utilisez le shell de manière incorrecte!); Par exemple, sur mon ordinateur, un script utilisant une boucle while pour compter jusqu'à un million (ne rien faire d'autre) est ~ 2x plus rapide dans mksh / zsh et> 2x plus rapide dans le tiret, mais dans un vrai script, je me déchargerais autant que possible vers d'autres programmes.
Loreb
3
bashl'habitude d'être très lent. Il a fait beaucoup de progrès récemment, mais pour la plupart des choses, il est encore plus lent que la plupart des autres obus.
Stéphane Chazelas
1
N'utilisez pas de bashisme simple . [ "$foo" != "${foo#*bar}" ]gère votre chose grep. Et la sedchose: while [ "$foo" != "${foo#*bar}" ]; do s=$s${foo%%bar*} foo=${foo#*bar} ; done ; foo=$s$foo. Vous pouvez mettre l'une ou l'autre chose dans une fonction.
mikeserv

Réponses:

39

SHELL SEQ:

Un moyen utile d’évaluer les performances d’un shell consiste probablement à effectuer de manière répétitive de très petites évaluations simples. Je pense qu’il est important, non seulement de boucler, mais de boucler une entrée , car un shell doit lire <&0.

Je pensais que cela compléterait les tests déjà publiés par @cuonglm, car il montre les performances d'un processus shell une fois invoquées, par opposition à la sienne, qui montre la rapidité avec laquelle un processus shell se charge. 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 nouvelle ligne ou, dans le cas d'une légère optimisation, s'il le peut, il incrémente 50 fois par nouvelle ligne. Chaque fois que la variable est incrémentée, elle est imprimée stdout. Cela se comporte beaucoup comme une sorte de seqcroix nl.

Et pour que ce soit très clair, voici une set -x;sortie tronquée après l'avoir insérée juste avant timedans 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 s'appelle d'abord comme:

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

... pour générer l'entrée qu'il devra boucler lorsqu'il lira 3<<\SCRIPT- ou quand le catfera quand même. Et de l’autre côté, |pipeil s’appelle encore comme:

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

Donc, mis à part l'appel initial à env (car il catest en fait appelé à la ligne précédente) ; aucun autre processus n'est appelé à partir du moment où il est appelé jusqu'à ce qu'il se termine. Au moins, j'espère que c'est vrai.

Avant les chiffres ...

Je devrais prendre quelques notes sur la portabilité.

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

  • mkshn'a pas printfintégré dans la plupart des cas. Les tests antérieurs avaient beaucoup de retard - il était invoquant /usr/bin/printfpour chaque course. D'où ce qui echo -nprécède.

  • 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 va les avoir tous 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

ARBITRARY = Peut-être OK?

Néanmoins, il s’agit d’un test plutôt arbitraire, mais il teste la lecture des entrées, l’évaluation arithmétique et l’expansion des variables. Peut-être pas complet, mais peut-être près de là.

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

  • Si vous avez besoin de vitesse, optez pour dash , il est beaucoup plus rapide que tout autre shell et environ 4x plus rapide que bash .
  • Alors que busybox shell de peut être beaucoup plus lent que tableau de bord , dans certains tests , il pourrait être plus rapide, car il a beaucoup de ses propres userland, comme grep, sed, sort, etc., qui ne sont pas autant de fonctionnalités que la GNU utilisée utilitaires, mais peut faire le travail autant.
  • Si la vitesse ne vous intéresse pas, ksh (ou ksh93 ) peut être considéré comme le meilleur compromis entre vitesse et fonctionnalités. Sa vitesse est comparable à celle du plus petit mksh , ce qui est bien plus rapide que bash , et présente également des caractéristiques uniques, telles que l' arithmétique en virgule flottante .
  • Bien que bash soit réputé pour sa simplicité, sa stabilité et ses fonctionnalités, il était le plus lent de tous les réservoirs dans la plupart de nos tests, et de loin.
Mikeserv
la source
Je ne parviens pas à faire fonctionner ce code dans bash (ainsi que ksh et zsh), mais uniquement dans dash, mksh et pdksh. Bash, j'ai essayé 4.2.37(1)-releasede Debian et 4.2.45(2)-released'un LiveCD Porteus (Slackware). Sans null=, au lieu de sortir des nombres, cela fonctionne comme si j’appuyais sur Entrée de façon continue, puis je dois tuer bash avec SIGKILL .
Teresa e Junior
Et j'ai aussi essayé avec bash --posix, en vain.
Teresa e Junior
@TeresaeJunior - que peut-être possible - bien que je ne pense pas que cela fonctionnera zsh. zshva le détourner ttyet bien, il va lancer un shell interactif. Je m'attends à ce que bashnous fassions de même - c'est pourquoi je veille à ne pas appeler que son --posixlien. Je peux le faire faire ce que vous attendez pour la plupart d'entre eux, mais cela peut représenter plus de travail que sa valeur. Appelez-vous bashou appelez -vous sh?
mikeserv
@TeresaeJunior Pouvez-vous entrer ici et poster la sortie? J'aimerais juste avoir une meilleure idée de ce qui se passe.
mikeserv
Ne devrais-je pas ajouter le texte de ma réponse au bas de la vôtre, pour la compléter, puis supprimer la mienne?
Teresa e Junior
20

Laissons faire un point de repère.

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 commence seulement un shell et ne fait rien avec l'opérateur no-op - deux points , puis quitte.

Comme le montre le résultat, il dashest extrêmement rapide bashau démarrage. dashest 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 d’une opération de démarrage. Laissons faire un autre repère:

$ 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 test simple 1 = 1, dashtoujours beaucoup plus rapide que bash.

cuonglm
la source
Votre réponse est très appréciée, mais il semble que vous ne mesuriez que la vitesse de démarrage du shell, mais pas sa rapidité d'exécution, n'est-ce pas?
Teresa e Junior
1
@TeresaeJunior: Oui, je ne parle que du temps de démarrage.
jeudi
Je suppose que seq 1 100000devrait être seq 1 1000?
Mikel
1
Mais pour votre dashcas de test c'est seulement seq 1 1000?
Mikel
Oh, désolé, c'est 1000pour le démarrage et 1000000pour fonctionner, réparé.
jeudi
7

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 afin de contrôler les boucles, de sorte 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, afin de s'assurer que l'exécutable du shell et les scripts sont en cache.

Comme vous pouvez le constater, il n'y a pas de gagnant net, mais 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 une source ouverte, il n'y a aucune raison de ne pas l'utiliser pour tout (des excuses si je comprends mal les subtilités en matière de licence): ksh93 est rapide, solide et une norme de facto dans UNIX-land (si ce n’est pas dans GNU / Linux-land); il fournit un sur-ensemble des fonctionnalités du shell POSIX (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
Alun Carr
la source
Ma conclusion utilisait aussi ksh93. Il est sous la licence publique commune, qui a été approuvée par la FSF.
Teresa e Junior
0

Il y a trop de cas tests inéquitables dans de nombreuses réponses ici. Si vous testez deux shells, utilisez la syntaxe correcte pour chacun d'eux. Et à Bash, les doubles supports sont beaucoup plus rapides et plus fiables que les supports simples, ce qui réduit considérablement la différence de vitesse. Également utiliser des bashismes optimisés et ces différences de vitesse sont plus moins aussi. Sur mon système, bash fonctionne comme un diable, avec un usage intensif des bashismes. Et les équivalents posix en tiret sont plus lents ici. Ce n’est pas correct que dash soit toujours plusieurs fois plus rapide que bash. Il est vraiment injuste de comparer les lignes de commande posix dans les deux cas, qui peut toujours être le plus rapide. À mon avis, Posix est lourdement obsolète. Et en termes de compatibilité, il est vraiment difficile de trouver des systèmes pertinents de nos jours, ils n’ont pas utilisé 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. Pas seulement la même ligne de commande, lorsqu'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, quel shell est plus rapide dans de nombreux cas d'utilisation.

Par exemple, pour remplacer tous les acaractères dans la chaîne avec des bpersonnages, en bash vous pouvez écrire "${varname//a/b}"tout au tableau de bord , vous devez appeler outil externe comme ceci: "$(echo "$varname" | sed 's/a/b/g')". Si vous devez le répéter quelques centaines de fois, l’utilisation du basisme peut vous donner une accélération 2x.

Jeff
la source
3
Avez-vous des exemples avec lesquels vous pourriez mettre à jour votre réponse pour montrer comment bash peut combler l'écart de performances ou même être plus rapide pour les tâches équivalentes? Votre réponse serait beaucoup plus forte avec quelques exemples spécifiques.
Eric Renouf