SHELL SEQ:
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() ( #don't 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.
Before the numbers...
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...
Anyway, to the numbers:
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
ARBITRARY = MAYBE OK?
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:
- If you need speed, go definitely with dash, it is much faster than any other shell and about 4x faster than bash.
- While busybox's shell can be much slower than dash, in some tests it could be faster, because it has many of its own userland utilities, like
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.
- If speed is not everything you care about, ksh (or ksh93) can be considered the best compromise between speed and features. It's speed compares to the smaller mksh, which is way faster than bash, and it has also some unique features, like floating point arithmetic.
- Although bash is famous for its simplicity, stability, and functionality, it was the slowest of all shells in the majority of our tests, and by a large margin.
[
should also be a builtin.bash
used to be very slow. It made a lot of progress recently, but for most things, it is still slower than most other shells.[ "$foo" != "${foo#*bar}" ]
handles your grep thing. And thesed
thing:while [ "$foo" != "${foo#*bar}" ]; do s=$s${foo%%bar*} foo=${foo#*bar} ; done ; foo=$s$foo
. You can put either thing into a function.