Czy dash lub inna powłoka jest „szybsza” niż bash?

57

Zawsze myślałem, że jedyną korzyścią z używania myślnika zamiast basha jest to, że myślnik był mniejszy, a zatem wiele wystąpień myślnika zaczynało się szybciej w czasie rozruchu.

Ale przeprowadziłem pewne badania i znalazłem ludzi migrujących wszystkie swoje skrypty, aby pobiegać w nadziei, że będą działać szybciej, i znalazłem to również w artykule DashAsBinSh na Ubuntu Wiki:

Głównym powodem zmiany domyślnej powłoki była wydajność . bash to doskonała, w pełni funkcjonalna powłoka odpowiednia do użytku interaktywnego; w rzeczywistości jest to nadal domyślna powłoka logowania. Jednak jest dość duży i powolny, aby uruchomić i działać w porównaniu z deską rozdzielczą.

Obecnie używam wielu skryptów bash do wielu rzeczy w moim systemie, a moim problemem jest to, że mam szczególny skrypt, który działam nieprzerwanie 24/7, który odradza około 200 dzieci, które razem ogrzewają mój komputer 10 ° C więcej niż podczas normalnego użytkowania.

Jest to dość duży skrypt z mnóstwem bashizmów, więc przeniesienie ich do POSIXa lub innej powłoki byłoby bardzo czasochłonne (a POSIX tak naprawdę nie ma znaczenia do użytku osobistego), ale byłoby warto, gdybym mógł zmniejszyć część tego Użycie procesora. Wiem, że są też inne rzeczy do rozważenia, takie jak wywołanie zewnętrznego pliku binarnego, takiego jak sedprosty bashizm ${foo/bar}, lub grepzamiast =~.

TL; DR jest naprawdę wolniejszy w uruchamianiu i działa w porównaniu z deską rozdzielczą? Czy istnieją inne powłoki Unix, które są bardziej wydajne niż bash?

Teresa e Junior
źródło
12
Jeśli zamierzasz przenieść go w celu zwiększenia wydajności, czy uważasz, że lepiej byłoby to zrobić w jakimkolwiek innym języku (perl, python, ruby) całkowicie? Są one na ogół znacznie wydajniejsze, jak sądzę, chociaż będzie to zależeć od dokładnej natury zadania.
goldilocks
Drobny punkt: [powinien być również wbudowany.
Mikel
2
Należy pamiętać, że w przeciwieństwie do zmartwień związanych z wykorzystaniem pamięci, różnica pokazuje głównie, jeśli wykonujesz obliczenia w powłoce, a nie w programach zewnętrznych (tj. Używasz powłoki w niewłaściwy sposób!); np. na moim komputerze skrypt używający pętli while do zliczenia do miliona (nic nie robiąc) jest ~ 2x szybszy w mksh / zsh i> 2x szybszy w desce rozdzielczej, ale w prawdziwym skrypcie odciążę jak najwięcej do innych programy.
loreb
3
bashKiedyś był bardzo wolny. Ostatnio poczyniono znaczne postępy, ale w większości przypadków jest on wolniejszy niż większość innych pocisków.
Stéphane Chazelas
1
Nie używaj prostego bashizmu . [ "$foo" != "${foo#*bar}" ]obsługuje twoją grep. I sedrzecz: while [ "$foo" != "${foo#*bar}" ]; do s=$s${foo%%bar*} foo=${foo#*bar} ; done ; foo=$s$foo. Możesz umieścić dowolną rzecz w funkcji.
mikeserv

Odpowiedzi:

39

SEQ SHELL:

Prawdopodobnie przydatnym sposobem oceny wydajności powłoki jest powtarzalne powtarzanie wielu bardzo małych, prostych ocen. Myślę, że ważne jest, aby nie tylko zapętlić, ale także zapętlić dane wejściowe , ponieważ powłoka musi czytać <&0.

Myślałem, że uzupełni to testy już opublikowane @cuonglm, ponieważ pokazuje wydajność pojedynczego procesu powłoki po jej wywołaniu, w przeciwieństwie do tego, który pokazuje, jak szybko ładuje się proces powłoki po wywołaniu. W ten sposób między nami pokrywamy obie strony monety.

Oto funkcja ułatwiająca pokaz:

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

Inkrementuje zmienną raz na odczyt nowego wiersza lub, w miarę możliwości, zwiększa 50 razy na odczyt nowego wiersza. Za każdym razem, gdy zmienna jest zwiększana, jest drukowana do stdout. Zachowuje się jak seqkrzyż nl.

I żeby wyjaśnić, co robi - oto niektóre skrócone set -x;dane wyjściowe po wstawieniu go tuż przed timepowyższą funkcją:

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

Tak więc każda powłoka jest najpierw nazywana:

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

... aby wygenerować dane wejściowe, które będą musiały być zapętlone, gdy wczyta się 3<<\SCRIPT- lub kiedy to catzrobi. Z drugiej strony |pipenazywa się ponownie:

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

Poza początkowym wywołaniem do env (ponieważ cattak naprawdę jest wywoływane w poprzedniej linii) ; żadne inne procesy nie są wywoływane od momentu jego wywołania do momentu wyjścia. Przynajmniej mam nadzieję, że to prawda.

Przed liczbami ...

Powinienem zrobić kilka notatek na temat przenośności.

  • poshnie lubi $((n=n+1))i nalega na$((n=$n+1))

  • mkshprintfw większości przypadków nie ma wbudowanego. Wcześniejsze testy znacznie go opóźniały - wywoływały /usr/bin/printfkażdy bieg. Stąd echo -npowyższe.

  • może bardziej, jak pamiętam ...

W każdym razie do liczb:

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

Wszystko to za jednym razem ...

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 = MOŻE BYĆ OK?

Jest to jednak raczej arbitralny test, ale testuje on odczyt danych, ocenę arytmetyczną i rozszerzanie zmiennych. Może nie wyczerpujące, ale być może blisko.

EDIT autorstwa Teresy e Junior : @mikeserv i ja przeprowadziliśmy wiele innych testów (szczegóły na naszym czacie ) i stwierdziliśmy, że wyniki można podsumować w następujący sposób:

  • Jeśli potrzebujesz prędkości, zdecydowanie skorzystaj z myślnika , jest ona znacznie szybsza niż jakakolwiek inna powłoka i około 4x szybsza niż uderzenie .
  • Choć BusyBox powłoki „s może być znacznie wolniejszy niż desce rozdzielczej , w niektórych testów może być szybciej, ponieważ ma wiele własnych narzędzi przestrzeni użytkownika, jak grep, sed, sort, itd., Które nie mają za wiele funkcji, jak powszechnie stosowanych GNU narzędzia, ale mogą zrobić tyle samo.
  • Jeśli prędkość to nie wszystko, na czym ci zależy, ksh (lub ksh93 ) można uznać za najlepszy kompromis między szybkością a funkcjami. Jego prędkość porównuje się z mniejszym mksh , który jest znacznie szybszy niż bash , a także ma kilka unikalnych cech, takich jak arytmetyka zmiennoprzecinkowa .
  • Chociaż bash słynie z prostoty, stabilności i funkcjonalności, był to najwolniejszy ze wszystkich powłok w większości naszych testów i z dużym marginesem.
mikeserv
źródło
Nie mogę zmusić tego kodu do działania w bash (a także ksh i zsh), tylko w dash, mksh i pdksh. Bash Próbowałem 4.2.37(1)-releasez Debiana i 4.2.45(2)-releasez LiveCD Porteus (Slackware). Bez null=, zamiast wypisywania liczb, działa tak, jakbym ciągle naciskał Return , wtedy muszę zabić Basha za pomocą SIGKILL .
Teresa e Junior
Próbowałem też bash --posix, ale bezskutecznie.
Teresa e Junior
@TeresaeJunior - to może możliwe - choć nie sądzę, aby to zadziałało zsh. zshprzejmie ttyi cóż, uruchomi interaktywną powłokę. Oczekuję, bashże zrobię to samo - dlatego ostrożnie nazywam tylko jego --posixlink. Mogę zrobić tak, jak oczekujesz dla większości z nich, ale może to być więcej pracy niż jest warte. Dzwonisz bashczy dzwonisz sh?
mikeserv
@TeresaeJunior można przyjść tutaj i zakładać wyjście? Chciałbym tylko dowiedzieć się, co się dzieje.
mikeserv
Czy nie powinienem dodawać tekstu mojej odpowiedzi na dole, aby go uzupełnić, a następnie usunąć mój?
Teresa e Junior
20

Zróbmy punkt odniesienia.

Z 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

Z 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

Każda iteracja uruchamia tylko powłokę i nic nie robi z operatorem no-op - dwukropkiem , a następnie kończy działanie.

Jak pokazują wyniki, dashjest niezwykle szybszy niż bashpodczas uruchamiania. dashjest mniejszy i zależy od mniej współdzielonej biblioteki niż 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)

Chodzi o czas uruchamiania, a co powiesz na działanie. Zróbmy kolejny punkt odniesienia:

$ 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

Z prostym testem 1 = 1, dashwciąż znacznie szybszym niż bash.

Cuonglm
źródło
Twoja odpowiedź jest bardzo doceniana, ale wydaje się, że mierzysz tylko szybkość uruchomienia powłoki, a nie tak naprawdę, jak szybko działa, prawda?
Teresa e Junior
1
@TeresaeJunior: Tak, wspominam tylko o czasie uruchamiania.
cuonglm
Zakładam, że seq 1 100000powinno być seq 1 1000?
Mikel
1
Ale w twoim dashprzypadku testowym to tylko seq 1 1000?
Mikel
Och, przepraszam, to jest 1000do uruchomienia i 1000000obsługi, naprawione.
cuonglm
7

Oto niektóre czasy uruchamiania różnych powłok w certyfikowanym systemie UNIX (Mac OS X 10.10.3). Przepisałem test, aby użyć tcsh do sterowania pętlami, aby testowana powłoka nie była tą kontrolującą pętle. Dla każdej powłoki pętla jest wykonywana pięć razy przed upływem czasu, aby upewnić się, że wykonywalna powłoka i skrypty są w pamięci podręcznej.

Jak widać, nie ma wyraźnego zwycięzcy, ale jest jeden ostateczny przegrany. W każdym razie, bash 4 jest wyraźnie wolniejszy niż bash 3. Dash działa dobrze, ale biorąc pod uwagę, że ksh93 jest teraz open source, nie ma prawdziwego powodu, aby nie używać go do wszystkiego (przepraszam, jeśli źle rozumiem jakiekolwiek elementy licencyjne): ksh93 jest szybki, solidny oraz de facto standard w UNIX-land (jeśli nie w GNU / Linux-land); zapewnia nadzbiór funkcjonalności powłoki POSIX (o ile rozumiem, powłoka POSIX była oparta na ksh88); jest równy bashowi jako interaktywnej powłoce, choć opóźnia się w porównaniu do tcsh. A przegrany to oczywiście 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
źródło
Mój wniosek również polegał na użyciu ksh93. Jest objęty wspólną licencją publiczną, która została zatwierdzona przez FSF.
Teresa e Junior
0

Istnieje wiele nieuczciwych przypadków testowych w wielu odpowiedziach tutaj. Jeśli przetestujesz dwie powłoki, użyj poprawnej składni dla każdej z nich. A w bash podwójne nawiasy są znacznie szybsze i bardziej niezawodne niż pojedyncze nawiasy, więc różnica prędkości jest znacznie mniejsza. Używaj również zoptymalizowanych bashism, a wtedy te różnice prędkości są również mniejsze. W moim systemie bash działa jak diabli, z dużym użyciem bashism. A ekwiwalenty posfiksu w myślniku są tutaj wolniejsze. To nie jest poprawne, że myślnik jest zawsze wielokrotnie szybszy niż uderzenie. Naprawdę niesprawiedliwe jest porównywanie linii poleceń POSIX-a w obu, których kreska zawsze może być najszybsza. Moim zdaniem posix jest mocno przestarzały. Jeśli chodzi o kompatybilność, obecnie bardzo trudno jest znaleźć odpowiednie systemy, nie używali powłoki bash.

Dobrym porównaniem jest: użycie najlepszego możliwego wiersza poleceń w każdej powłoce, aby zakończyć określone zadanie. Nie tylko dokładnie ta sama linia poleceń, gdy tylko jedna powłoka ma tutaj przewagę. Takie porównania są niewiarygodne i nie pokazują prawdziwej wydajności konkurentów. W mojej codziennej pracy widzę, która powłoka jest szybsza w wielu przypadkach użycia.

Na przykład, aby zastąpić wszystkie aznaki z łańcucha bznaków, w bash można napisać "${varname//a/b}"natomiast w desce rozdzielczej trzeba zadzwonić zewnętrznego narzędzia takiego: "$(echo "$varname" | sed 's/a/b/g')". Jeśli musisz to powtórzyć kilkaset razy - użycie bashizmu może dać 2x przyspieszenie.

Jeff
źródło
3
Czy masz jakieś przykłady, za pomocą których możesz zaktualizować swoją odpowiedź, aby pokazać, w jaki sposób bash może zniwelować różnicę w wydajności lub nawet przyspieszyć wykonywanie równoważnych zadań? Twoja odpowiedź byłaby o wiele silniejsza w przypadku niektórych konkretnych przykładów.
Eric Renouf,