web-dev-qa-db-ja.com

ダッシュや他のシェルはbashより「速い」のですか?

Bashの代わりにdashを使用する唯一の利点は、dashが小さいことであり、したがって、多くのdashのインスタンスは起動時により速く起動するだろうといつも思っていました。

しかし、いくつかの調査を行ったところ、すべてのスクリプトを移行してダッシュが速くなることを期待してダッシュする人がいることがわかりました。また、Ubuntu Wikiの記事 DashAsBinSh でも見つかりました。

デフォルトのシェルを切り替える主な理由は 効率。 bashは、インタラクティブな使用に適した優れたフル機能のシェルです。実際、それはまだデフォルトのログインシェルです。しかし、それはかなり大きく、 起動が遅い そして操作する ダッシュと比較して。

この頃私は私のシステムの多くのもののために多くのbashスクリプトを使用してきました、そして私の問題は私が24時間年中無休で実行している特定のスクリプトがあり、それが一緒に私のコンピューターを10°加熱する約200の子供を生み出すことです通常の使用よりもCが多い。

これは多くのバシズムを持つかなり大きなスクリプトなので、それらをPOSIXや他のシェルに移植するのは非常に時間がかかります(そしてPOSIXは実際には個人的な使用では問題になりません)が、これを減らすことができれば価値がありますCPU使用率。 _${foo/bar}_のような単純なバシズムのためにsedのような外部バイナリを呼び出す、または_=~_の代わりにgrepを呼び出すなど、他にも考慮すべき点があることを知っています。

TL; DRは、起動するのが本当に遅いです そして操作する ダッシュと比較して? bashよりも効率的なUnixシェルは他にありますか?

57
Teresa e Junior

シェルSEQ:

おそらく、シェルのパフォーマンスをベンチマークする便利な方法は、非常に小規模で単純な評価を繰り返し実行することです。シェルは_<&0_を読み取る必要があるため、ループするだけでなくinputをループすることが重要だと思います。

これはテストを補完するものだと思いました @ cuonglmはすでに投稿されています 呼び出されたときにシェルプロセスがどれだけ速くロードされるかを示すのとは対照的に、一度呼び出された単一のシェルプロセスのパフォーマンスを示します。このように、私たちの間で、私たちはコインの両面をカバーします。

デモを容易にする関数は次のとおりです。

_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
_

改行の読み取りごとに1回変数を増分するか、可能であれば、わずかな最適化として、改行の読み取りごとに50回増分します。変数がインクリメントされるたびに、stdoutに出力されます。 seq cross _nlとよく似ています。

そして、それが何をするかを非常に明確にするために-上記の関数のtimeの直前に挿入した後の一部の切り捨てられた_set -x;_出力があります:

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

したがって、各シェルは最初に次のように呼び出されます。

_ 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))を主張します

  • mkshには、ほとんどの場合printfが組み込まれていません。以前のテストでは、大幅な遅延がありました-すべての実行で_/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
_

任意=たぶんOK?

それでも、これはかなり恣意的なテストですが、入力の読み取り、算術評価、変数展開をテストします。多分包括的ではないかもしれませんが、おそらくそこに近いでしょう。

EDITE by Teresa e Junior:@mikeservと私は他の多くのテストを実行しました(詳細は 私たちのチャット を参照してください)。結果は次のように要約できることがわかりました:

  • 速度が必要な場合は、dashを使用してください。他のシェルよりもはるかに高速で、bash
  • busyboxのシェルはdashよりもはるかに遅くなる可能性がありますが、一部のテストではより速くなる可能性があります。 grepsedsortなどの独自のユーザーランドユーティリティの多くを備えているため、一般的に使用されているGNUユーティリティ。ただし、同じくらいの作業を行うことができます。
  • 速度が気になるすべてではない場合、ksh(またはksh93)が最良の妥協案と見なすことができます速度と機能の間。速度はmkshと比べて高速であり、bashよりもはるかに高速で、次のようないくつかのユニークな機能も備えています- 浮動小数点演算
  • bashは、そのシンプルさ、安定性、および機能性で有名ですが、大部分のテストでは、すべてのシェルの中で最も遅く、マージンが大きかった。
39
mikeserv

ベンチマークをしましょう。

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

各反復はシェルを開始するだけで、no-op演算子- colon で何もせずに終了します。

結果が示すように、dashは起動時にbashよりも非常に高速です。 dashは小さく、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)

これは起動時間についてであり、どのように動作しますか。別のベンチマークをやってみましょう:

$ 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 = 1dashbashよりもはるかに高速です。

20
cuonglm

以下は、認定されたUNIX(Mac OS X 10.10.3)でのさまざまなシェルの起動タイミングです。 tcshを使用してループを制御するようにテストを書き直したので、テストされているシェルがループを制御するシェルではありませんでした。各シェルに対して、タイミングの前に5回ループが実行され、シェル実行可能ファイルとスクリプトがキャッシュにあることを確認します。

ご覧のとおり、明確な勝者はありませんが、決定的な敗者が1人います。とにかく、bash 4はbash 3よりも明らかに低速です。Dashは十分に機能しますが、ksh93がオープンソースになったことを考えると、これをすべてに使用しないという本当の理由はありません(ライセンスの細かい点を誤解した場合はお詫び):ksh93は高速で堅実です、およびUNIXランドの事実上の標準(GNU/Linuxランドでない場合)。 POSIXシェル機能のスーパーセットを提供します(私が理解している限り、POSIXシェルはksh88に基づいていました)。 tcshに比べると遅れますが、対話型シェルとしてのbashと同じです。そして敗者はもちろん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
7
Alun Carr

ここの多くの回答には、不当なテストケースが多すぎます。 2つのシェルをテストする場合は、それぞれに正しい構文を使用してください。また、bashでは、ダブルブラケットはシングルブラケットよりもはるかに高速で信頼性が高いため、速度の違いはまったくありません。また、最適化されたバシズムを使用すると、これらの速度の違いも少なくなります。私のシステムでは、bashは地獄のように動作し、bashismを多用しています。そして、ダッシュでのposixの同等物はここでは遅くなります。これは正しくありません。ダッシュは常にbashよりも数倍高速です。実際、両方のposixコマンドラインを比較するのはかなり不公平です。ダッシュは常に最速です。私の見解では、posixはかなり古くなっています。そして、互換性の面では、今日、関連するシステムを見つけるのは非常に難しく、bash Shellを使用していませんでした。

適切な比較は、各シェルで可能な限り最高のコマンドラインを使用して、特定のジョブを完了することです。 1つのシェルだけが実際にここで利点を持っている場合、まったく同じコマンドラインだけでなく、このような比較は信頼できず、競合他社の実際のパフォーマンスを示していませんでした。私は毎日の仕事を見ていますが、シェルは多くのユースケースでより高速です。

たとえば、文字列内のすべてのa文字をb文字に置き換えるには、bashでは_"${varname//a/b}"_と記述し、ダッシュでは次のような外部ツールを呼び出す必要があります。"$(echo "$varname" | sed 's/a/b/g')"。数百回繰り返す必要がある場合、bashismを使用すると2倍のスピードアップが得られます。

0
jeff

アルゴリズムのソリューションを必要とする合理的に効率的な素朴な問題とは対照的に、すべてのパフォーマンスの問題が効率の問題であるとは限りません。全体でどちらか一方を修正する場合、最も効率的なソリューションが最高のパフォーマンスを発揮するのは、その数倍です。

問題を修正するための適切な方法は誰にも教えられませんが、POSIXシェルが解決できる特定の効率問題が1つあります。これが、すべての起動スクリプトがBashからDashに移植された理由です。

多数のスクリプトが同時に起動するためにパフォーマンスの問題が発生しているが、それらのスクリプトを単独で使用するとかなり効率的である場合は、それらすべてをPOSIXシェルに移植することをお勧めします。

ただし、実際には一度に非常に多くのプロセスを生成する必要があること、およびスクリプトの特定の部分を別の方法で書き換えることができないことを確認するために、最初にダブルチェックを行う可能性があります。生成された200の子プロセスが他のスクリプトであるかどうかは言及していませんが、より大きな親スクリプトの代わりにそれらを単に移植することが可能かもしれません。

0
J. M. Becker