Ist dash oder eine andere Shell "schneller" als bash?

57

Ich war immer der Meinung, dass der einzige Vorteil der Verwendung von dash anstelle von bash darin besteht, dass dash kleiner ist und daher viele Instanzen von dash beim Booten schneller gestartet werden.

Aber ich habe einige Nachforschungen angestellt und festgestellt, dass einige Leute all ihre Skripte migrieren, um in der Hoffnung, dass sie schneller laufen, zu stürzen. Dies fand ich auch im Artikel DashAsBinSh im Ubuntu-Wiki:

Der Hauptgrund für den Wechsel der Standard-Shell war die Effizienz . bash ist eine exzellente Shell mit vollem Funktionsumfang, die für den interaktiven Gebrauch geeignet ist. In der Tat ist es immer noch die Standard-Login-Shell. Es ist jedoch im Vergleich zum Armaturenbrett ziemlich groß und langsam in Betrieb zu nehmen und zu betreiben .

Heutzutage verwende ich viele Bash-Skripte für viele Dinge auf meinem System, und mein Problem ist, dass ich ein bestimmtes Skript habe, das ständig rund um die Uhr ausgeführt wird und rund 200 Kinder hervorbringt, die zusammen meinen Computer um 10 ° erwärmen C mehr als im normalen Gebrauch.

Es ist ein ziemlich großes Skript mit vielen Bashismen, so dass das Portieren auf POSIX oder eine andere Shell sehr zeitaufwendig wäre (und POSIX ist für den persönlichen Gebrauch eigentlich nicht von Bedeutung), aber es lohnt sich, wenn ich etwas davon reduzieren könnte CPU auslastung. Ich weiß, dass es auch andere Dinge zu beachten gibt, wie das Aufrufen einer externen Binärdatei wie sedfür einen einfachen Bashismus wie ${foo/bar}oder grepanstelle von =~.

TL; DR ist wirklich langsamer zu starten und im Vergleich zu Dash zu betreiben ? Gibt es andere Unix-Shells, die effizienter sind als Bash?

Teresa und Junior
quelle
12
Wenn Sie es für die Leistung portieren möchten, denken Sie, dass es in einer anderen Sprache (Perl, Python, Ruby) besser ist? Sie sind im Allgemeinen viel effizienter, denke ich, obwohl es von der genauen Art der Aufgabe abhängt.
Goldlöckchen
Minor point: [sollte auch eingebaut sein.
Mikel
2
Denken Sie daran, dass sich der Unterschied im Gegensatz zur Sorge um die Speichernutzung hauptsächlich ergibt, wenn Sie Berechnungen in der Shell und nicht in externen Programmen ausführen (dh wenn Sie die Shell falsch verwenden!). Beispiel: Auf meinem Computer ist ein Skript, das eine while-Schleife verwendet, um auf eine Million zu zählen (sonst nichts), in mksh / zsh ~ 2x schneller und in dash> 2x schneller, aber in einem echten Skript würde ich so viel wie möglich an andere auslagern Programme.
Loreb
3
bashwar früher sehr langsam. Es hat in letzter Zeit große Fortschritte gemacht, aber für die meisten Dinge ist es immer noch langsamer als die meisten anderen Shells.
Stéphane Chazelas
1
Verwenden Sie keine einfachen Bashismen . [ "$foo" != "${foo#*bar}" ]kümmert sich um deine grep Sache. Und die sedSache: while [ "$foo" != "${foo#*bar}" ]; do s=$s${foo%%bar*} foo=${foo#*bar} ; done ; foo=$s$foo. Sie können beides in eine Funktion einfügen.
mikeserv

Antworten:

39

SHELL SEQ:

Wahrscheinlich ist es ein nützliches Mittel, die Leistung einer Shell zu messen, sehr kleine, einfache Auswertungen, die wiederholt durchgeführt werden. Ich denke, es ist wichtig, nicht nur eine Schleife auszuführen, sondern eine Schleife über die Eingabe auszuführen , da eine Shell lesen muss <&0.

Ich dachte, dies würde die bereits veröffentlichten Tests von @cuonglm ergänzen , da es die Leistung eines einzelnen Shell-Prozesses zeigt, wenn er einmal aufgerufen wurde, im Gegensatz zu seinem, der zeigt, wie schnell ein Shell-Prozess geladen wird, wenn er aufgerufen wird. Auf diese Weise decken wir beide Seiten der Medaille ab.

Hier ist eine Funktion, um die Demo zu erleichtern:

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

Es erhöht eine Variable entweder einmal pro Zeilenumbruch oder, als leichte Optimierung, um das 50-fache pro Zeilenumbruch. Jedes Mal, wenn die Variable inkrementiert wird, wird auf gedruckt stdout. Es verhält sich wie eine Art seqKreuz nl.

Und um ganz klar zu machen, was es tut - hier ist eine abgeschnittene set -x;Ausgabe, nachdem Sie sie direkt zuvor timein die obige Funktion eingefügt haben:

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

So heißt jede Shell zuerst:

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

... um die Eingabe zu generieren, die beim Einlesen durchlaufen werden muss 3<<\SCRIPT- oder catzumindest wann . Und andererseits |pipenennt es sich wieder so:

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

Also abgesehen von dem ersten Aufruf an env (weil catin der vorherigen Zeile tatsächlich aufgerufen wird) ; Vom Zeitpunkt des Aufrufs bis zum Beenden werden keine anderen Prozesse aufgerufen. Zumindest hoffe ich, dass das stimmt.

Vor den Zahlen ...

Ich sollte ein paar Anmerkungen zur Portabilität machen.

  • poshmag nicht $((n=n+1))und besteht darauf$((n=$n+1))

  • mkshhat printfin den meisten Fällen kein eingebautes. Frühere Tests hatten viel Nachholbedarf - es wurde /usr/bin/printffür jeden Lauf aufgerufen . Daher das echo -nObige.

  • vielleicht mehr, als ich mich erinnere ...

Sowieso zu den Zahlen:

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

Das bringt sie alle auf einen Streich ...

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 = MÖGLICHERWEISE OK?

Dies ist immer noch ein eher willkürlicher Test, der jedoch Leseeingaben, arithmetische Auswertungen und Variablenerweiterungen testet. Vielleicht nicht umfassend, aber möglicherweise in der Nähe von dort.

EDIT von Teresa e Junior : @mikeserv und ich haben viele andere Tests durchgeführt ( Einzelheiten finden Sie in unserem Chat ), und wir haben festgestellt, dass die Ergebnisse folgendermaßen zusammengefasst werden können:

  • Wenn Sie Geschwindigkeit brauchen, gehen Sie auf jeden Fall mit Dash , es ist viel schneller als jede andere Shell und etwa 4x schneller als Bash .
  • Während Busybox ‚s Schale viel langsamer als sein Strich , in einigen Tests könnte es schneller sein, weil sie viele ihrer eigenen Benutzerprogramme hat, wie grep, sed, sortusw., die als die üblicherweise verwendeten GNU nicht so viele Funktionen haben Dienstprogramme, aber kann die Arbeit so viel erledigen.
  • Wenn Geschwindigkeit nicht alles ist, was Sie interessiert, kann ksh (oder ksh93 ) als der beste Kompromiss zwischen Geschwindigkeit und Funktionen angesehen werden. Die Geschwindigkeit ist vergleichbar mit dem kleineren mksh , das viel schneller als bash ist , und es hat auch einige einzigartige Funktionen, wie z . B. Fließkomma-Arithmetik .
  • Obwohl bash für seine Einfachheit, Stabilität und Funktionalität bekannt ist, war es bei den meisten Tests die langsamste aller Shells und das mit großem Abstand.
mikeserv
quelle
Ich kann diesen Code nicht in bash (und auch ksh und zsh) zum Laufen bringen, nur in dash, mksh und pdksh. Bash habe ich 4.2.37(1)-releasevon Debian und 4.2.45(2)-releasevon einer Porteus LiveCD (Slackware) aus versucht . Ohne null=statt Zahlen der Ausgabe, es funktioniert , als ob ich gedrückt Return kontinuierlich, dann muss ich bash mit töten SIGKILL .
Teresa e Junior
Und ich habe es auch versucht bash --posix, ohne Erfolg.
Teresa e Junior
@TeresaeJunior - das ist vielleicht möglich - obwohl ich nicht glaube, dass es funktionieren wird zsh. zshwird entführen ttyund gut, es wird eine interaktive Shell starten. Ich bashgehe davon aus, dass dies auch so sein wird - weshalb ich darauf achte, nur den --posixLink aufzurufen . Ich kann es für die meisten von ihnen so machen, wie Sie es erwarten, aber es kann mehr Arbeit sein, als es wert ist. Rufst du an bashoder rufst du an sh?
mikeserv
@TeresaeJunior Kannst du hier reinkommen und die Ausgabe posten? Ich möchte nur eine bessere Vorstellung davon bekommen, was passiert.
mikeserv
Sollte ich den Text meiner Antwort nicht unten in Ihre hinzufügen, um ihn zu ergänzen, und dann meine löschen?
Teresa e Junior
20

Machen wir einen Benchmark.

Mit 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

Mit 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

Jede Iteration startet nur eine Shell und führt nichts mit dem Operator no-op aus - Doppelpunkt , dann beenden.

Wie das Ergebnis zeigt, dashist es extrem schneller als bashbeim Start. dashist kleiner und von weniger gemeinsam genutzten Bibliotheken abhängig als 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)

Hier geht es um die Startzeit, wie wäre es mit Betrieb. Machen wir noch einen 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

Mit einfachen Test 1 = 1, dashnoch viel schneller als bash.

cuonglm
quelle
Ihre Antwort wird sehr geschätzt, aber anscheinend messen Sie nur, wie schnell die Shell startet, nicht wirklich, wie schnell sie funktioniert, oder?
Teresa e Junior
1
@TeresaeJunior: Ja, ich erwähne nur die Startzeit.
Donnerstag,
Ich nehme an, seq 1 100000sollte sein seq 1 1000?
Mikel
1
Aber für deinen dashTestfall ist es nur seq 1 1000?
Mikel
Oh, tut mir leid, es ist 1000für den Start und 1000000für den Betrieb, behoben.
Donnerstag,
7

Hier einige Startzeiten für verschiedene Shells unter einem zertifizierten UNIX (Mac OS X 10.10.3). Ich habe den Test neu geschrieben, um tcsh zur Steuerung der Schleifen zu verwenden, sodass die zu testende Shell nicht diejenige war, die die Schleifen steuert. Für jede Shell wird die Schleife fünfmal vor dem Timing ausgeführt, um sicherzustellen, dass sich die ausführbare Shell-Datei und die Skripte im Cache befinden.

Wie Sie sehen, gibt es keinen eindeutigen Gewinner, aber einen definitiven Verlierer. Jedenfalls ist bash 4 deutlich langsamer als bash 3. Dash bietet eine gute Leistung, aber da ksh93 jetzt Open Source ist, gibt es keinen wirklichen Grund, es nicht für alles zu verwenden (entschuldige mich, wenn ich irgendwelche Lizenzierungsprobleme missverstehe): ksh93 ist schnell, solide , und ein De-facto-Standard in UNIX-Land (wenn nicht in GNU / Linux-Land); es bietet eine Obermenge der POSIX-Shell-Funktionalität (soweit ich weiß, basierte die POSIX-Shell auf ksh88); es ist gleichbedeutend mit bash als interaktive Shell, obwohl es im Vergleich zu tcsh hinterherhinkt. Und der Verlierer ist natürlich 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
quelle
Mein Fazit war auch die Verwendung von ksh93. Es steht unter der Common Public License, die von der FSF genehmigt wurde.
Teresa e Junior
0

Es gibt hier zu viele unfaire Testfälle in vielen Antworten. Wenn Sie zwei Shells testen, verwenden Sie für jede die richtige Syntax. Und in bash sind Doppelklammern viel schneller und zuverlässiger als Einzelklammern, so dass es überhaupt keinen Geschwindigkeitsunterschied gibt. Verwenden Sie auch optimierte Bashismen, um die Geschwindigkeitsunterschiede zu verringern. Auf meinem System läuft die Bash wie die Hölle, mit starkem Gebrauch von Bashismen. Und Posix-Äquivalente im Strich sind hier langsamer. Dies ist nicht korrekt, da der Bindestrich immer um ein Vielfaches schneller ist als die Bash. Wirklich ist es ziemlich unfair, Posix-Befehlszeilen in beiden zu vergleichen, wobei Strich immer der schnellste sein kann. Meiner Meinung nach ist posix stark veraltet. Und was die Kompatibilität angeht, ist es heutzutage sehr schwierig, relevante Systeme zu finden. Sie verwendeten keine Bash-Shell.

Ein guter Vergleich besteht darin, die bestmögliche Befehlszeile in jeder Shell zu verwenden, um einen bestimmten Job zu beenden. Nicht nur genau die gleiche Kommandozeile, wenn hier wirklich nur eine Shell einen Vorteil hat. Derartige Vergleiche sind unzuverlässig und zeigten nicht die tatsächliche Leistung der Wettbewerber. Ich sehe bei meiner täglichen Arbeit, welche Shell in vielen Anwendungsfällen schneller ist.

Um zum Beispiel alle ersetzen aZeichen in Zeichenkette mit bZeichen, in bash Sie können schreiben , "${varname//a/b}"während in dash Sie externes Tool wie folgt verlangen: "$(echo "$varname" | sed 's/a/b/g')". Wenn Sie es ein paar hundert Mal wiederholen müssen - mit Bashismus können Sie 2x beschleunigen.

Jeff
quelle
3
Haben Sie Beispiele, mit denen Sie Ihre Antwort aktualisieren können, um zu zeigen, wie Bash die Leistungslücke schließen oder sogar die entsprechenden Aufgaben schneller erledigen kann? Ihre Antwort wäre mit einigen konkreten Beispielen viel stärker.
Eric Renouf