I always thought that the only benefit of using dash instead of bash was that dash was smaller, and therefore many instances of dash would start faster at boot time.
But I have done some research, and found some people migrating all their scripts to dash in the hope they would run faster, and I also found this in the article DashAsBinSh in the Ubuntu Wiki:
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.
Nowadays I've been using lots of bash scripts for many things on my system, and my problem is that I have a particular script that I'm running continuously 24/7, that spawns around 200 children, which together heat my computer 10°C more than in normal usage.
It is a rather large script with lots of bashisms, so porting them to POSIX or some other shell would be very time consuming (and POSIX doesn't really matter for personal use), but it would be worth if I could reduce some of this CPU usage. I know there are also other things to consider, like calling an external binary like sed
for a simple bashism like ${foo/bar}
, or grep
instead of =~
.
TL;DR is really bash slower to start up and operate in comparison with dash? Are there other Unix shells which are more efficient than bash?
Probably a useful means of bench-marking a shell's performance is to do a lot of very small, simple evaluations repetitively. It is important, I think, not just to loop, but to loop over input, because a shell needs to read <&0
.
I thought this would complement the tests @cuonglm already posted because it demonstrates a single shell process's performance once invoked, as opposed to his which demonstrates how quickly a shell process loads when invoked. In this way, between us, we cover both sides of the coin.
Here's a function to facilitate the demo:
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
It either increments a variable once per newline read or, as a slight-optimization, if it can, it increments 50 times per newline read. Every time the variable is incremented it is printed to stdout
. It behaves a lot like a sort of seq
cross nl
.
And just to make it very clear what it does - here's some truncated set -x;
output after inserting it just before time
in the function above:
time env - /usr/bin/busybox ash -c '
while echo; do echo; done |
/usr/bin/busybox ash -c '"'$(
cat <&3
)'"' -- 20 5 busybox'
So each shell is first called like:
env - $shell -c "while echo; do echo; done |..."
...to generate the input that it will need to loop over when it reads in 3<<\SCRIPT
- or when cat
does, anyway. And on the other side of that |pipe
it calls itself again like:
"...| $shell -c '$(cat <<\SCRIPT)' -- $args"
So aside from the initial call to env
(because cat
is actually called in the previous line); no other processes are invoked from the time it is called until it exits. At least, I hope that's true.
I should make some notes on portability.
posh
doesn't like $((n=n+1))
and insists on $((n=$n+1))
mksh
doesn't have a printf
builtin in most cases. Earlier tests had it lagging a great deal - it was invoking /usr/bin/printf
for every run. Hence the echo -n
above.
maybe more as I remember it...
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
That'll get 'em all in one go...
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
Still, this is a rather arbitrary test, but it does test reading input, arithmetic evaluation, and variable expansion. Maybe not comprehensive, but possibly near to there.
EDIT by Teresa e Junior: @mikeserv and I have done many other tests (see our chat for details), and we found the results could be summarized like this:
grep
, sed
, sort
, etc., which don't have as many features as the commonly used GNU utilities, but can get the work done as much.Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments