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?
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.
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 ...
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
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:
grep
, sed
, sort
, etc., qui n'ont pas autant de fonctionnalités que le GNU utilitaires, mais peut faire le travail autant.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
.
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
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.
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.