dash 或其他一些 shell 比 bash “更快”吗?

dash 或其他一些 shell 比 bash “更快”吗?

我一直认为使用 dash 而不是 bash 的唯一好处是 dash 更小,因此 dash 的许多实例在启动时启动得更快。

但我做了一些研究,发现有些人将所有脚本迁移到 dash,希望它们运行得更快,我也在文章中发现了这一点达世币在 Ubuntu 维基百科中:

切换默认 shell 的主要原因是效率。 bash 是一个优秀的全功能 shell,适合交互式使用;事实上,它仍然是默认的登录 shell。不过,它的规模相当大,而且启动慢并操作与破折号相比。

如今,我在系统上的许多事情上使用了大量的 bash 脚本,我的问题是我有一个特定的脚本,我每天 24/7 连续运行,它会产生大约 200 个子脚本,它们一起将我的计算机加热 10° C 比正常使用时多。

这是一个相当大的脚本,有很多 bashism,因此将它们移植到 POSIX 或其他一些 shell 会非常耗时(POSIX 对于个人使用来说并不重要),但如果我可以减少其中的一些,那就值得了CPU使用率。我知道还有其他事情需要考虑,比如调用外部二进制文件来sed调用简单的 bashism,例如${foo/bar}, 或grep代替=~

长话短说bash 启动速度确实较慢并操作与破折号相比?还有其他更强大的 Unix shell 吗?高效的比bash?

答案1

外壳序列:

也许对 shell 性能进行基准测试的一种有用方法是重复进行大量非常小的、简单的评估。我认为重要的是不仅要循环,还要循环输入,因为 shell 需要读取<&0

我认为这可以补充测试@cuonglm 已发布因为它演示了单个 shell 进程在调用后的性能,而不是他的演示了 shell 进程在调用时加载的速度。这样,我们之间就涵盖了硬币的两面。

这是一个方便演示的函数:

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

它要么在每次换行读取时递增变量一次,要么作为轻微优化(如果可以的话),在每次换行读取时递增 50 次。每次变量递增时,都会将其打印到stdout.它的行为很像一种seq十字架nl

为了非常清楚地说明它的作用 - 这是在上面的函数set -x;之前插入它后的一些截断输出:time

time env - /usr/bin/busybox ash -c '
     while echo; do echo; done |
     /usr/bin/busybox ash -c '"'$(
         cat <&3
     )'"' -- 20 5 busybox'

所以每个 shell 首先被调用如下:

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

...生成在读入时需要循环的输入3<<\SCRIPT- 或者cat无论如何。另一方面,|pipe它再次调用自己,如下所示:

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

所以除了最初的电话env (因为cat实际上是在上一行中调用的);从调用它到退出,不会调用任何其他进程。至少,我希望这是真的。

在数字之前...

我应该就可移植性做一些笔记。

  • posh不喜欢$((n=n+1))并坚持$((n=$n+1))

  • mkshprintf在大多数情况下没有内置的。早期的测试让它有很大的滞后——/usr/bin/printf每次运行都会调用它。于是就有了echo -n以上的情况。

  • 也许更多,因为我记得......

无论如何,从数字来看:

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

这样一来就能把他们全部搞定了...

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

    

任意=也许可以?

尽管如此,这是一个相当随意的测试,但它确实测试了读取输入、算术评估和变量扩展。也许不全面,但可能接近那里。

编辑:Teresa e Junior:@mikeserv 和我做了很多其他测试(参见我们的聊天详细信息),我们发现结果可以总结如下:

  • 如果你需要速度,一定要选择短跑,它比任何其他 shell 都快得多,大约比巴什
  • 尽管忙碌盒的 shell 可能比短跑,在某些测试中它可能会更快,因为它有许多自己的用户态实用程序,如grepsedsort等,它们没有常用的 GNU 实用程序那么多的功能,但可以完成同样多的工作。
  • 如果速度不是你关心的一切克什(或者克什93)可以被认为是速度和功能之间的最佳折衷。它的速度与较小的相比姆克什,这比巴什,而且它还有一些独特的功能,比如浮点运算
  • 虽然巴什它以其简单性、稳定性和功能性而闻名,但在我们的大多数测试中,它是所有 shell 中最慢的,而且速度相差很大。

答案2

让我们做一个基准测试。

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

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

每次迭代仅启动一个 shell,并且对无操作运算符不执行任何操作 -冒号,然后退出。

结果显示,比启动时dash要快得多。比 更小,并且依赖更少的共享库:bashdashbash

$ 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)

这是关于启动时间,操作如何。让我们做另一个基准测试:

$ 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

经过简单测试1 = 1dash还是比 快很多bash

答案3

以下是经过认证的 UNIX (Mac OS X 10.10.3) 中各种 shell 的一些启动时序。我重写了测试以使用 tcsh 来控制循环,以便被测试的 shell 不是控制循环的 shell。对于每个 shell,循环在计时之前执行五次,以确保 shell 可执行文件和脚本位于缓存中。

正如您所看到的,没有明确的赢家,但有一个明确的输家。无论如何,bash 4 明显比 bash 3 慢。Dash 表现良好,但考虑到 ksh93 现在是开源的,没有真正的理由不将它用于所有用途(如果我误解了任何许可细节,请道歉):ksh93 快速、稳定,以及 UNIX 领域事实上的标准(如果不在 GNU/Linux 领域的话);它提供了 POSIX shell 功能的超集(据我所知,POSIX shell 基于 ksh88);作为交互式 shell,它与 bash 相同,但与 tcsh 相比有所落后。而输家当然是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

答案4

这里的很多答案中有太多不公平的测试用例。如果测试两个 shell,则为每个 shell 使用正确的语法。在 bash 中,双括号比单括号更快、更可靠,因此速度差异要小得多。还可以使用优化的 bashisms,然后这些速度差异也会更小。在我的系统上,bash 运行得非常糟糕,并且大量使用了 bashism。而 dash 中的 posix 等价物在这里速度较慢。这是不正确的,因为 dash 总是比 bash 快好几倍。确实,比较两者的 posix 命令行是相当不公平的,谁的 dash 总是最快的。在我看来 posix 已经严重过时了。而且在兼容性方面,现在确实很难找到相关的系统,他们没有使用bash shell。

一个很好的比较是:在每个 shell 中使用尽可能好的命令行来完成特定的工作。不仅是完全相同的命令行,只有一个 shell 在这里确实具有优势。这样的比较是不可靠的,并且不能显示竞争对手的真实表现。我在日常工作中看到,在许多用例中哪个 shell 更快。

例如,要将a字符串中的所有字符替换为b字符,在 bash 中您可以编写,"${varname//a/b}"而在 dash 中则必须调用外部工具,如下所示:"$(echo "$varname" | sed 's/a/b/g')"。如果你必须重复几百次 - 使用 bashism 可以给你 2 倍的加速。

相关内容