Дефіс чи якась інша оболонка «швидша», ніж баш?


57

Я завжди думав, що єдиною перевагою використання тире замість bash було те, що дефіс був меншим, і тому багато екземплярів тире почнеться швидше під час завантаження.

Але я провів деякі дослідження, і виявив, що люди мігрують всі свої сценарії, щоб вони забігали, сподіваючись, що вони будуть працювати швидше, і я також це знайшов у статті DashAsBinSh в Ubuntu Wiki:

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

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

Це досить великий сценарій з великою кількістю башизмів, тому перенесення їх на POSIX або якусь іншу оболонку буде забирати багато часу (а POSIX насправді не має значення для особистого використання), але варто було б, якби я міг зменшити частину цього Використання процесора. Я знаю, що є й інші речі, які слід враховувати, як, наприклад, називати зовнішній бінарний на зразок sedпростого башизму, як ${foo/bar}, або grepзамість цього =~.

TL; DR насправді забивається повільніше, щоб запустити та працювати в порівнянні з тире? Чи є інші снаряди Unix, які ефективніші, ніж ударні?


12
Якщо ви збираєтесь перенести його для виконання, чи вважаєте ви, що це краще зробити цілком іншою мовою (perl, python, ruby) цілком? Як правило, вони набагато ефективніші, хоча це залежатиме від конкретного характеру завдання.
goldilocks

Незначна точка: [також повинна бути вбудованою.
Мікель

2
Майте на увазі, що на відміну від вас турбує використання пам'яті, різниця в основному виявляється, якщо ви робите обчислення в оболонці, а не у зовнішніх програмах (тобто ви використовуєте оболонку неправильно!); наприклад, на моєму комп’ютері сценарій, що використовує цикл "time" для підрахунку до мільйона (нічого не роблячи), ~ mx швидше в mksh / zsh і> 2x швидше в тирі, але в реальному сценарії я б максимально завантажувався на інші програми.
loreb

3
bashзвикли дуже повільно. Нещодавно він значно просунувся, але для більшості речей він все-таки повільніше, ніж більшість інших снарядів.
Стефан Шазелас

1
Не використовуйте простих башизмів . [ "$foo" != "${foo#*bar}" ]обробляє вашу річ. І sedголовне: while [ "$foo" != "${foo#*bar}" ]; do s=$s${foo%%bar*} foo=${foo#*bar} ; done ; foo=$s$foo. Ви можете поставити будь-яку річ у функцію.
mikeserv

Відповіді:


39

ПОШУКОВИЙ ШЕК:

Напевно, корисним засобом тестування оболонки на стенді є те, щоб робити багато дуже маленьких, простих оцінок повторно. Думаю, важливо не просто циклічно, а перетворити цикл на вхід , оскільки оболонку потрібно читати <&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

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

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

EDIT від Teresa e Junior : @mikeserv і я зробили багато інших тестів (детальніше див. Наш чат ), і ми виявили, що результати можна підсумувати так:

  • Якщо вам потрібна швидкість, обов'язково рухайтеся з тире , це набагато швидше, ніж будь-яка інша оболонка і приблизно в 4 рази швидше, ніж удар .
  • У той час як BusyBox оболонка «s може бути набагато повільніше , ніж тир , в деяких тестах він може бути швидше, тому що він володіє багатьма зі своїх власних користувальницьких утиліт, як grep, sed, sortі т.д., які не має стількох можливостей , як правило , використовуються GNU комунальні послуги, але можна зробити роботу якнайбільше.
  • Якщо швидкість - це не все, що вам дуже важливо, ksh (або ksh93 ) можна вважати найкращим компромісом між швидкістю та можливостями. Його швидкість порівнюється з меншим мкш , який швидше, ніж bash , і він також має деякі унікальні особливості, наприклад, арифметика з плаваючою комою .
  • Хоча баш славиться своєю простотою, стабільністю та функціональністю, він був найбільш повільним з усіх оболонок у більшості наших тестів та великим запасом.

Я не можу змусити цей код працювати в bash (а також ksh і zsh), лише в тире, mksh і pdksh. Bash Я спробував 4.2.37(1)-releaseвід Debian і 4.2.45(2)-releaseвід Porteus LiveCD (Slackware). Без null=замість виведення чисел це працює так, як ніби я постійно натискав Return , тоді мені доведеться вбивати баш за допомогою SIGKILL .
Teresa e Junior

І я теж намагався bash --posix, безрезультатно.
Teresa e Junior

@TeresaeJunior - це, можливо, можливо, хоча я не вірю, що це буде працювати zsh. zshбуде викрадати ttyі добре, він запустить інтерактивну оболонку. Я очікую bash, що зробимо те саме - саме тому я обережно називати лише його --posixпосилання. Я можу змусити це зробити так, як ви очікуєте для більшості будь-якого з них, але це може бути більше роботи, ніж варто. Ви телефонуєте bashчи дзвоните sh?
mikeserv

@TeresaeJunior Чи можете ви зайти сюди і розмістити вихід? Я просто хотів би краще зрозуміти, що відбувається.
mikeserv

Чи не повинен я додати текст своєї відповіді до нижньої частини вашої, щоб доповнити її, а потім видалити мою?
Teresa e Junior

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

Кожна ітерація лише запускає оболонку і нічого не робить з оператором без операції - двокрапка , а потім вийдіть.

Як показує результат, 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.


Ваша відповідь дуже цінується, але, здається, ви вимірюєте лише те, наскільки швидко працює запуск оболонки, а не дуже швидко, як вона працює, правда?
Teresa e Junior

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-land (якщо не в GNU / Linux-land); він забезпечує набір функцій оболонки 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. Це під загальною публічною ліцензією, яка була затверджена ФФС.
Teresa e Junior

0

У багатьох відповідях тут є занадто багато несправедливих тестових випадків. Якщо випробовуєте дві оболонки, то використовуйте правильний синтаксис для кожної з них. А в bash подвійні дужки набагато швидші та надійніші, ніж у одиночних дужок, тому взагалі набагато менша різниця в швидкості. Також використовуйте оптимізовані башизми, і тоді ці різниці швидкості теж менше. У моїй системі баш працює як пекло, з великим використанням басизмів. А еквіваленти posix у тирі тут повільніші. Це невірно, що тире завжди в кілька разів швидше, ніж баш. Дійсно, досить несправедливо порівнювати командні рядки posix в обох, хто тире завжди може бути найшвидшим. На мій погляд, позикс важко застарів. Що стосується сумісності, то зараз важко знайти відповідні системи, вони не використовували оболонку bash.

Хороше порівняння: використовувати найкращий можливий командний рядок у кожній оболонці, щоб закінчити певну роботу. Тут не тільки той самий командний рядок, коли лише одна оболонка справді має перевагу. Такі порівняння ненадійні і не показували реальних показників конкурентів. На своїй щоденній роботі я бачу, яка оболонка швидша у багатьох випадках використання.

Наприклад, щоб замінити всі aсимволи в рядку з bсимволами, в БАШЕЄВ ви можете написати в "${varname//a/b}"той час як в тирі ви повинні викликати зовнішній інструмент , як це: "$(echo "$varname" | sed 's/a/b/g')". Якщо вам доведеться повторити це кілька сотень разів - використання башизму може дати вам 2x прискорення.


3
Чи є у вас приклади, з якими можна оновити свою відповідь, щоб показати, як баш може закрити розрив у продуктивності або навіть бути швидшим при рівнозначних завданнях? Ваша відповідь була б набагато сильнішою з деякими конкретними прикладами.
Ерік Реноф
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.