Dash или какая-то другая оболочка «быстрее» bash?

57

Я всегда думал, что единственным преимуществом использования тире вместо bash является то, что тире меньше, и поэтому во многих случаях тире запускается быстрее во время загрузки.

Но я провел некоторое исследование и обнаружил, что некоторые люди переносят все свои сценарии в надежде, что они будут работать быстрее, и я также нашел это в статье DashAsBinSh в Ubuntu Wiki:

Основной причиной переключения оболочки по умолчанию была эффективность . bash - превосходная полнофункциональная оболочка, подходящая для интерактивного использования; действительно, это все еще оболочка входа по умолчанию. Тем не менее, он довольно большой и медленно запускается и работает по сравнению с тире.

В настоящее время я использую множество сценариев bash для многих вещей в моей системе, и моя проблема в том, что у меня есть определенный сценарий, который я запускаю непрерывно 24/7, который порождает около 200 детей, которые вместе нагревают мой компьютер на 10 ° C больше, чем при обычном использовании.

Это довольно большой скрипт с множеством ошибок, поэтому его перенос на POSIX или какую-либо другую оболочку будет очень трудоемким (и POSIX не имеет особого значения для личного использования), но было бы целесообразно, если бы я мог уменьшить часть этого Использование процессора. Я знаю, что есть и другие вещи, которые нужно учитывать, например, вызывать внешний двоичный файл как sedдля простого bashism, например ${foo/bar}, или grepвместо =~.

TL; DR - действительно ли bash медленнее запускается и работает по сравнению с dash? Существуют ли другие оболочки Unix, более эффективные, чем bash?

Тереза ​​и Джуниор
источник
12
Если вы собираетесь портировать его для повышения производительности, как вы думаете, было бы лучше сделать это на каком-то другом языке (perl, python, ruby) полностью? Я думаю, что в целом они гораздо более эффективны, хотя это будет зависеть от точного характера задачи.
Златовласка
Незначительный момент: [также должен быть встроенным.
Микель
2
Имейте в виду, что в отличие от того, что вас беспокоит использование памяти, разница в основном проявляется, если вы выполняете вычисления в оболочке, а не во внешних программах (т. Е. Вы используете оболочку неправильно!); например, на моем компьютере скрипт, использующий цикл while для подсчета до миллиона (ничего не делая), в ~ 2 раза быстрее в mksh / zsh и> в 2 раза быстрее в тире, но в реальном скрипте я бы разгрузил как можно больше для других программы.
Loreb
3
bashРаньше был очень медленным. В последнее время он добился большого прогресса, но в большинстве случаев он все еще медленнее, чем большинство других оболочек.
Стефан Шазелас
1
Не используйте простой башизм . [ "$foo" != "${foo#*bar}" ]обрабатывает вашу вещь grep. И sedглавное: while [ "$foo" != "${foo#*bar}" ]; do s=$s${foo%%bar*} foo=${foo#*bar} ; done ; foo=$s$foo. Вы можете поместить любую вещь в функцию.
mikeserv

Ответы:

39

SHELL SEQ:

Вероятно, полезный способ оценки производительности оболочки - многократно выполнять очень простые простые оценки. Я думаю, что важно не просто зацикливать, а зацикливать ввод , потому что оболочка должна читать <&0.

Я думал, что это дополнит тесты, которые @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

Он либо увеличивает переменную один раз за чтение новой строки, либо, в случае небольшой оптимизации, если это возможно, увеличивает ее до 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'

Итак, каждая оболочка сначала называется так:

 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

АРБИТРАЖ = МОЖЕТ ОК?

Тем не менее, это довольно произвольный тест, но он проверяет входные данные чтения, арифметическую оценку и расширение переменной. Возможно не всесторонний, но возможно близко к там.

РЕДАКТИРОВАТЬ Teresa e Junior : @mikeserv и я провели много других тестов (подробности см. В нашем чате ), и мы обнаружили, что результаты можно суммировать следующим образом:

  • Если вам нужна скорость, определенно используйте dash , она намного быстрее любой другой оболочки и примерно в 4 раза быстрее, чем bash .
  • В то время как BusyBox оболочка «s может быть гораздо медленнее , чем тир , в некоторых тестах он может быть быстрее, потому что он обладает многими из своих собственных пользовательских утилит, как grep, sed, sortи т.д., которые не имеют так много особенностей , как обычно используются GNU коммунальные услуги, но можно сделать работу столько же.
  • Если скорость - это не все, что вас волнует, ksh (или ksh93 ) можно считать лучшим компромиссом между скоростью и функциями. Его скорость сравнивается с меньшим mksh , который намного быстрее, чем bash , и у него также есть некоторые уникальные особенности, такие как арифметика с плавающей запятой .
  • Хотя bash славится своей простотой, стабильностью и функциональностью, он был самым медленным из всех оболочек в большинстве наших тестов и с большим отрывом.
mikeserv
источник
Я не могу заставить этот код работать в bash (а также в ksh и zsh), только в dash, mksh и pdksh. Bash я пробовал 4.2.37(1)-releaseиз Debian и 4.2.45(2)-releaseиз Porteus LiveCD (Slackware). Без null=, вместо вывода чисел, это работает так, как будто я постоянно нажимаю Return , тогда мне приходится убивать bash с помощью SIGKILL .
Тереза ​​и младший
И я тоже пытался bash --posix, но безрезультатно.
Тереза ​​и младший
@TeresaeJunior - это возможно возможно - хотя я не верю, что это сработает zsh. zshбудет угонять ttyи хорошо, он запустит интерактивную оболочку. Я ожидаю, bashчто сделаю то же самое - вот почему я осторожен, чтобы назвать только его --posixссылку. Я могу сделать так, как вы ожидаете, для большинства из них, но это может быть больше работы, чем стоит. Ты звонишь bashили звонишь sh?
mikeserv
@TeresaeJunior Можете ли вы прийти сюда и опубликовать вывод? Я просто хотел бы получить лучшее представление о том, что происходит.
mikeserv
Разве я не должен добавить текст своего ответа внизу вашего, дополнить его, а затем удалить свой?
Тереза ​​и младший
20

Давай сделаем эталон.

С 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 - двоеточие , а затем завершается.

Как показывает результат, он 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 = 1, dashвсе еще намного быстрее, чем bash.

cuonglm
источник
Ваш ответ очень важен, но кажется, что вы измеряете только скорость запуска оболочки, а не скорость ее работы, верно?
Тереза ​​и младший
1
@TeresaeJunior: Да, я упоминаю только о времени запуска.
cuonglm
Я полагаю, seq 1 100000должно быть seq 1 1000?
Микель
1
Но для вашего dashтеста это только seq 1 1000?
Микель
Ой, извините, это 1000для запуска и 1000000для работы, исправлено.
Cuonglm
7

Вот некоторые моменты запуска различных оболочек в сертифицированной UNIX (Mac OS X 10.10.3). Я переписал тест, чтобы использовать tcsh для управления циклами, чтобы тестируемая оболочка не была той, которая контролировала циклы. Для каждой оболочки цикл выполняется пять раз перед синхронизацией, чтобы гарантировать, что исполняемый файл оболочки и сценарии находятся в кэше.

Как видите, нет однозначного победителя, но есть один окончательный проигравший. В любом случае, bash 4 заметно медленнее, чем bash 3. Dash работает хорошо, но, учитывая, что ksh93 теперь открыт с открытым исходным кодом, нет реальной причины не использовать его для всего (извиняюсь, если я неправильно понимаю какие-либо тонкости лицензирования): ksh93 быстрый, надежный и де-факто стандарт в земле UNIX (если не в GNU / Linux-земле); он предоставляет расширенный набор функций оболочки POSIX (насколько я понимаю, оболочка POSIX была основана на ksh88); он равен bash как интерактивная оболочка, хотя и запаздывает по сравнению с tcsh. И проигравший, конечно, зш.

/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
Алун Карр
источник
Мой вывод тоже использовал ksh93. Он находится под Общей публичной лицензией, которая была одобрена ФФС.
Тереза ​​и Юниор
0

Здесь слишком много несправедливых тестовых примеров во многих ответах. Если вы тестируете две оболочки, используйте правильный синтаксис для каждой из них. А в bash двойные скобки намного быстрее и надежнее, чем одинарные, поэтому разница в скорости намного меньше. Также используйте оптимизированные bashisms, и тогда эти различия в скорости также более менее. В моей системе Bash работает как ад, с интенсивным использованием bashisms. И эквиваленты posix в тире здесь медленнее. Это не правильно, что тире всегда в несколько раз быстрее, чем bash. Действительно несправедливо сравнивать командные строки posix в обеих, которые всегда могут быть самыми быстрыми. На мой взгляд, posix сильно устарел. И с точки зрения совместимости, сегодня действительно сложно найти соответствующие системы, они не использовали оболочку bash.

Хорошее сравнение: использовать наилучшую возможную командную строку в каждой оболочке, чтобы завершить определенную работу. Не только точно такая же командная строка, когда только одна оболочка действительно имеет здесь преимущество. Подобные сравнения ненадежны и не показывают реальных результатов конкурентов. Я вижу на своей повседневной работе, какая оболочка быстрее во многих случаях использования.

Например, чтобы заменить все aсимволы в строке с bсимволами, в Баше вы можете написать в "${varname//a/b}"то время как в тире вы должны вызвать внешний инструмент , как это: "$(echo "$varname" | sed 's/a/b/g')". Если вам придется повторять это несколько сотен раз - использование bashism может дать вам 2-кратное ускорение.

Джефф
источник
3
У вас есть примеры, с помощью которых вы могли бы обновить свой ответ, чтобы показать, как bash может сократить разрыв в производительности или даже ускорить выполнение аналогичных задач? Ваш ответ будет намного сильнее с некоторыми конкретными примерами.
Эрик Ренуф,