69

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?

admirabilis
  • 4,712
  • 16
    If you are going to port it for performance, do you think it would be better done in some other language (perl, python, ruby) entirely? They are generally much more efficient, I think, although it will depend on the exact nature of the task. – goldilocks Aug 02 '14 at 13:23
  • Minor point: [ should also be a builtin. – Mikel Aug 02 '14 at 13:48
  • @Mikel I thought it wasn't a builtin in dash, but it seems it is too. Fixed. – admirabilis Aug 02 '14 at 14:00
  • Iirc when (like, years ago) debian's dpkg-reconfigure offered the option to pick dash/bash as /bin/sh, picking dash improved boot time significantly (like 50% reduction), but you lose all the convenient bashisms... just follow goldilocks' advice: if it's worth switching, switch to some non-shell. – loreb Aug 02 '14 at 17:06
  • 2
    Keep in mind that unlike you're worried about memory usage the difference mostly shows if you're doing computations in the shell rather than in external programs (ie, you're using the shell the wrong way!); eg on my computer a script using a while loop to count to a million (doing nothing else) is ~2x faster in mksh/zsh and >2x faster in dash, but in a real script I'd offload as much as possible to other programs. – loreb Aug 02 '14 at 17:16
  • 4
    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. – Stéphane Chazelas Aug 02 '14 at 18:55
  • 1
    Dont use simple bashism's. [ "$foo" != "${foo#*bar}" ] handles your grep thing. And the sed 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. – mikeserv Aug 02 '14 at 23:14
  • I disagree that perl/python/ruby is usually the best way to go - you still have to start either one of those things. For very complex tasks, the answer lies there, yes, but for simple things - especially simple repetitive things - clean, POSIX shell script is probably the the most expedient answer. – mikeserv Aug 02 '14 at 23:29
  • mksh, by the way, is pretty quick as well, and tends to support the majority of the bashisms you might be used to. – mikeserv Aug 02 '14 at 23:36
  • Numbers illustrate the wiki you presented. For instance git ls-files | grep '\.c' | xargs wc -l to figure out lines of C I guess(or use cloc). In summary(bash/dash): repo objects: 153MiB/442KiB; LOC (C): 140 697/17 061; binary: 766/104K. To what extent this impacts startup and execution is beyond me, plus it doesn't say anything about the qualitative side, but it's interesting! –  Aug 03 '14 at 09:39
  • dash is not useful for a UNIX system, as it does not implement support for multi byte characters. The lmited features would allow dash just to be used on embedded systems and note that dash is the slowest shell I am aware of, since adding support for multi byte characters would make it slower than bash. The fastest shell is ksh93 in the version that is published with OpenSolaris. The second fastest shell is bosh, the POSIXified Bourne Shell. It supports multi byte characters and is still faste than the current dash. – schily Apr 07 '20 at 10:52

7 Answers7

49

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.
mikeserv
  • 58,310
  • I can't get this code to work in bash (and also ksh and zsh), only in dash, mksh, and pdksh. Bash I have tried 4.2.37(1)-release from Debian and 4.2.45(2)-release from a Porteus LiveCD (Slackware). Without null=, instead of outputting numbers, it works as if I pressed Return continuously, then I have to kill bash with SIGKILL. – admirabilis Aug 03 '14 at 03:52
  • And I have also tried with bash --posix, to no avail. – admirabilis Aug 03 '14 at 03:57
  • @TeresaeJunior - that maybe possible - though I don't believe it will work with zsh. zsh will hijack the tty and well, it will launch an interactive shell. I expect bash will do the same - which is why I am careful to only call its --posix link. I can make it do as you expect for most any of them, but it may be more work than its worth. Are you calling bash or are you calling sh? – mikeserv Aug 03 '14 at 03:58
  • @TeresaeJunior Can you come in here and post the output? I'd just like to get a better idea of what is happening. – mikeserv Aug 03 '14 at 04:01
  • Shouldn't I add the text of my answer to the bottom of yours, to complement it, and then delete mine? – admirabilis Aug 04 '14 at 00:13
  • I have posted it a few minutes ago, but that's more of a conclusion I would include in the original question, but found it would fit better in an answer. – admirabilis Aug 04 '14 at 00:35
  • @TeresaeJunior - yeah, I see it now. I've upvoted it - so there's that, but if you feel it fits better here, I have no objections. Edit away. – mikeserv Aug 04 '14 at 00:35
  • OK! [12 more characters to go...] – admirabilis Aug 04 '14 at 00:36
25

Let do a benchmark.

With 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

With 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

Each iteration only start a shell and do nothing with no-op operator - colon, then quit.

As the result show, dash is extremely faster than bash at startup. dash is smaller, and depend on less shared library than 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)

This is about startup time, how about operate. Let do another benchmark:

$ 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

With simple test 1 = 1, dash still much faster than bash.

cuonglm
  • 153,898
10

Here's some startup timings of various shells in a certified UNIX (Mac OS X 10.10.3). I rewrote the test to use tcsh to control the loops so that the shell being tested was not the one controlling the loops. For each shell the loop is executed five times prior to timing, to ensure that the shell executable and the scripts are in cache.

As you can see, there's no clear-cut winner, but there's one definitive loser. Anyhow, bash 4 is distinctly slower than bash 3. Dash performs well, but given that ksh93 is now open-source, there's no real reason not to use it for everything (apologies if I misunderstand any licensing niceties): ksh93 is fast, solid, and a de-facto standard in UNIX-land (if not in GNU/Linux-land); it provides a superset of the POSIX shell functionality (as far as I understand, the POSIX shell was based on ksh88); it is equal to bash as an interactive shell, though lagging compared to tcsh. And the loser is of course 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
  • 161
  • 1
  • 4
2

subshell benchmark is for sh/dash :

time ./sh.sh   --> real  0m12.382s
time ./dash.sh --> real  0m12.459s
time ./bash.sh --> real  0m48.913s (Cannot allocate memory)

K=$( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( $( exit ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
Michel
  • 131
1

There are too many unfair test cases in many answers here. If test two shells then use the correct syntax for each of them. And in bash doublebrackets are much faster and more reliable than singlebrackets, so there is a much less speed difference at all. Also use optimized bashisms and then these speed differences are more less too. On my system bash runs like hell, with heavy use of bashisms. And posix equivalents in dash are slower here. This is not correct that dash is always multiple times faster than bash. Really it is pretty unfair to compare posix command lines in both, who dash can always be the fastest. In my view posix is heavy outdated. And in terms of compatibility, it is really hard to found relevant systems nowdays, they didn't use a bash shell.

A good comparison is: to use the best possible command line in each shell, to finish a specific job. Not only exactly the same command line, when only one shell really has an advantage here. Comparisons like this are unreliable and didn't show the real performance of the competitors. I see at my every day job, which shell is faster in many use cases.

For example, to replace all a characters in string with b characters, in bash you can write "${varname//a/b}" while in dash you have to call external tool like this: "$(echo "$varname" | sed 's/a/b/g')". If you have to repeat it few hundred times - using bashism can give you 2x speedup.

jeff
  • 52
  • 6
    Do you have any examples you could update your answer with to show how bash can close the performance gap or even be faster on the equivalent tasks? Your answer would be a lot stronger with some specific examples. – Eric Renouf Jun 10 '17 at 11:15
1

Not all performance problems are efficiency problems, just the majority of them are, in contrast to reasonably efficient naive problems which require algorithmic solutions. If we were to fix one or the other in totality, more times than not the most efficient solution has the best performance.

Nobody can tell you the proper path to correcting the problem, but there is one specific efficiency problem POSIX shell can solve, which is the reason all the startup scripts were ported from Bash to Dash.

If you are experiencing performance problems because of large numbers of scripts starting-up at the same time, but they are all fairly reasonably efficient each on their own, then porting them all to POSIX shell is a preferable solution.

However I'd likely first double check to make sure you actually must be spawning so many processes at once, and that the specific part of the script cannot be re-written differently. You don't mention if the 200 spawned children processes are other scripts, it might be possible to just port them instead of the larger parent script.

J. M. Becker
  • 4,891
1

Expanding on @Michel answer:

$ cat se.bash       # for stack-exchange
#/bin/bash
n="${1:-100}"

unset a; a='exit';

for((i=1;i<=n;i++)); do printf -va '$(%s)' "$a" ; done;

f=./se.tst.sh echo "Z=&quot;$a&quot;" >"$f"

for sh in yash dash bash zsh do printf '%-20s: ' "$sh"; time $sh ./"$f"; done

$ ./se.bash 500 yash : run : 0m1.812s sec dash : run : 0m1.194s sec bash : run : 0m3.225s sec zsh : run : 0m21.892s sec

Which makes the script easier to reproduce and to use with a variable number of subshells.

Note that ksh93 (even being the fastest to evaluate empty subshells) is limited to 256 sub-shells only.