Зачем избегать подоболочек?

Я видел много ответов и комментариев о переполнении стека, в которых упоминается, что нужно что-то делать, чтобы избежать подоболочки. В некоторых случаях для этого приводится функциональная причина (чаще всего потенциальная необходимость чтения переменной вне подоболочки, которая была назначена внутри нее), но в других случаях избегание рассматривается как самоцель. Например

Почему это? Для стиля/элегантности/красоты? Для производительности (избегая вилки)? Для предотвращения возможных ошибок? Что-то другое?


person ruakh    schedule 24.02.2014    source источник
comment
Этот вопрос опасно широк и основан на мнениях, но я думаю, что это в основном из соображений производительности. В конце концов, подоболочка разветвляется в другом процессе.   -  person nwellnhof    schedule 24.02.2014
comment
@nwellnhof: я не думаю, что это широко или основано на мнениях, чтобы спросить, почему существует определенное мнение. Я думаю, что было бы широко, если бы я спросил, почему кто-то будет придерживаться этого мнения (вместо того, почему люди придерживаются); и я думаю, что это будет основано на мнении, если я спрошу мнение людей о подоболочках; но как бы то ни было, я ожидаю, что это будет довольно конкретно и ответственно.   -  person ruakh    schedule 24.02.2014
comment
Одна из причин — производительность. Разветвление новой оболочки — нетривиальная операция.   -  person Gene    schedule 24.02.2014
comment
Настоящие подоболочки появляются в списке процессов с тем же именем, что и родительская оболочка. Для скриптов, использующих их много (и позволяющих им работать долго), таблица процессов заполняется довольно бесполезной информацией.   -  person uli42    schedule 30.05.2018
comment
Есть две причины, по которым я избегаю подоболочек: производительность и потеря данных переменных среды. Если подоболочка вызывается в каком-либо цикле, для каждого вызова есть как стоимость вилки (для выполнения подоболочки), так и стоимость установки (для перехода от запущенного к готовности к выполнению работы) и стоимость уничтожения (для ее завершения). подоболочки. Более серьезной проблемой для меня всегда было то, что переменные подоболочки умирают вместе с подоболочкой, что очень затрудняет получение сложных результатов работы из подоболочки (часто требуя временных файлов и добавляя еще больше накладных расходов). Также: PID меняется с каждой подоболочкой.   -  person Jody Bruchon    schedule 09.12.2020


Ответы (4)


Происходит несколько вещей.

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

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

См. BashFAQ #24 для некоторых примеров неожиданного поведения, вызванного подоболочками.

person Charles Duffy    schedule 24.02.2014

иногда примеры полезны.

f='fred';y=0;time for ((i=0;i<1000;i++));do if [[ -n "$( grep 're' <<< $f )" ]];then ((y++));fi;done;echo $y

real    0m3.878s
user    0m0.794s
sys 0m2.346s
1000

f='fred';y=0;time for ((i=0;i<1000;i++));do if [[ -z "${f/*re*/}" ]];then ((y++));fi;done;echo $y

real    0m0.041s
user    0m0.027s
sys 0m0.001s
1000

f='fred';y=0;time for ((i=0;i<1000;i++));do if grep -q 're' <<< $f ;then ((y++));fi;done >/dev/null;echo $y

real    0m2.709s
user    0m0.661s
sys 0m1.731s
1000

Как видите, в этом случае разница между использованием grep в подоболочке и расширением параметров для выполнения одного и того же базового теста почти в 100 раз превышает общее время.

Следуя дальше вопросу и принимая во внимание приведенные ниже комментарии, которые явно не указывают на то, что они пытаются указать, я проверил следующий код: https://unix.stackexchange.com/questions/284268/какие-накладныерасходы-использования-подоболочек

time for((i=0;i<10000;i++)); do echo "$(echo hello)"; done >/dev/null 
real    0m12.375s
user    0m1.048s
sys 0m2.822s

time for((i=0;i<10000;i++)); do echo hello; done >/dev/null 
real    0m0.174s
user    0m0.165s
sys 0m0.004s

Это на самом деле намного хуже, чем я ожидал. Фактически почти на два порядка медленнее по общему времени и почти на ТРИ порядка медленнее по времени системных вызовов, что абсолютно невероятно. https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html

Обратите внимание, что цель демонстрации этого состоит в том, чтобы показать, что если вы используете метод тестирования, который довольно легко привыкнуть к использованию, grep подоболочки, или sed, или gawk (или встроенный bash, например, echo), который для меня плохая привычка, к которой я склонен быстро взламывать, стоит понимать, что это приведет к значительному снижению производительности, и, вероятно, стоит избегать их, если встроенные функции bash могут справиться с работой изначально.

Тщательно изучив использование подоболочек в больших программах и по возможности заменив их другими методами, я смог сократить примерно 10% общего времени выполнения только что выполненного набора оптимизаций (не первого и не последнего, раз я это сделал, его уже несколько раз оптимизировали, так что выигрыш еще 10% на самом деле весьма значителен)

Так что стоит быть в курсе.

Поскольку мне было любопытно, я хотел подтвердить, что «время» говорит нам здесь: https://en.wikipedia.org/wiki/Time_(Unix)

Общее время ЦП представляет собой комбинацию количества времени, которое ЦП или ЦП тратят на выполнение некоторых действий для программы, и количества времени, которое они тратят на выполнение системных вызовов для ядра от имени программы. Когда программа перебирает массив, она накапливает процессорное время пользователя. И наоборот, когда программа выполняет системный вызов, такой как exec или fork, она накапливает системное процессорное время.

Как вы можете видеть, в частности, в тесте эхо-цикла, стоимость форков очень высока с точки зрения системных вызовов ядра, эти форки действительно складываются (в 700 раз!!! больше времени тратится на системные вызовы).

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

person Lizardx    schedule 29.07.2017
comment
Подождите, но разница между двумя вашими примерами намного больше, чем у одного из них есть подоболочка. Тот, у кого подоболочка, также вызывает внешнюю программу (а именно grep), а также захватывает вывод подоболочки в строку. - person ruakh; 30.07.2017
comment
Да, но это было бы очень легко сделать, чтобы получить такой же точный результат в тесте. На самом деле, я именно этим и занимался, когда решил протестировать его и посмотреть, какова реальная разница в скорости/производительности. Тщательно выискивая такие ловушки, я смог повысить производительность очень большого скрипта примерно на 10%. Технически я мог бы протестировать функцию в сценарии, чтобы увидеть, в чем будет заключаться эта разница, хотя я это сделал, и она все еще довольно существенна, как отмечали другие, подоболочки очень дороги. У меня есть привычка возвращать строку функции в bash. - person Lizardx; 31.07.2017
comment
Итак, используя ваши команды в качестве отправной точки, я протестировал четыре версии: (1) регулярное выражение Bash, без подоболочки; (2) grep -q, подоболочки нет; (3) регулярное выражение Bash, лишняя подоболочка; и (4) grep внутри подстановки команды. Я обнаружил, что № 2 занял примерно три четверти длины, а № 3 — только около одной четверти. Таким образом, вы сильно преувеличиваете преимущества удаления подоболочки. - person ruakh; 31.07.2017
comment
Всегда стоит что-то тестировать. Тем не менее, на мой взгляд, вы склонны к педантизму, и все правы, моя точка зрения была довольно ясной и очевидной, и прирост производительности, который я увидел, не был фантазией. Я думаю, что иногда можно пропустить лес за деревьями, это то, с чем стоит быть осторожным, имея дело с этими очень бинарными неестественными системами. Я тоже люблю подвергать сомнению предположения. Я не могу обнаружить в ваших словах ничего, что противоречило бы приведенным выше фактам, данные были довольно четкими, между этими двумя методами разница в результатах была огромной. - person Lizardx; 31.07.2017
comment
Я обновил с помощью grep -q, спасибо, это немного лучше, чем grep в подоболочке, примерно в 70 раз медленнее. Первый пример был примерно в 95 раз медленнее, чем чистый вариант bash без подоболочки. Так что подумайте, прежде чем использовать эти методы тестирования, они распространены и легко становятся вредными привычками. - person Lizardx; 31.07.2017

ну, вот моя интерпретация того, почему это важно: это ответ №2!

прирост производительности немалый, даже если речь идет об отказе от одной подоболочки... Назовите меня мистером Очевидностью, но концепция, лежащая в основе этого мышления, та же, что и в том, чтобы избегать бесполезного использования <insert tool here>, например cat|grep, sort|uniq или даже cat|sort|uniq и т. д..

Эта концепция представляет собой философию Unix, которая ESR хорошо подытожен со ссылкой на KISS: Будь проще, глупец!

Я имею в виду, что если вы пишете сценарий, вы никогда не знаете, как он может быть использован в конце, поэтому важен каждый маленький байт или цикл, который вы можете выделить, поэтому, если ваш сценарий в конечном итоге потребляет миллиарды строк ввода, то он будет на столько же разветвлений/байтов/… более оптимизированным.

person zmo    schedule 24.02.2014
comment
Я удивлен, услышав ваше объяснение KISS как поддержку микрооптимизации (каждого байта или цикла); по моему опыту, его чаще используют для поддержки противоположной точки зрения. - person ruakh; 24.02.2014
comment
ну, мое понимание слова «простой» является этимологическим и означает, что сложность дизайна должна быть простой. Что в большинстве случаев не является синонимом легкого. Здесь использование двух fork(), когда необходим только один, добавляет сложности. - person zmo; 24.02.2014
comment
KISS относится к простому дизайну, а не к несложным в вычислительном отношении алгоритмам — если вам нужно потратить еще пару циклов процессора только для того, чтобы ваш код было легче читать и понимать, KISS говорит, что вы должны это сделать. - person mgarciaisaia; 24.02.2014
comment
моя интерпретация принципа KISS применительно к философии Unix состоит в том, что мы всегда должны пытаться найти баланс между читабельностью и (не-)сложностью. - person zmo; 24.02.2014
comment
@mgarciaisaia, действительно. Однако оболочка, в частности, полна ловушек и побочных эффектов — например, foo "$bar" длиннее (больше символов), чем foo $bar, но более предсказуема, поскольку сообщает оболочке избегать фаз обработки разделения строк и расширения универсальных объектов. Более экстремальный пример — while IFS='' read -r -d ''; do ...; done < <(find ... -print0), который выглядит как значительный объем накладных расходов, но значительно уменьшает место для ошибок. Избегание подоболочек похоже, поскольку это уменьшает пространство для неожиданного поведения (см., например, mywiki.wooledge.org/ BashFAQ/024). - person Charles Duffy; 25.02.2014
comment
@CharlesDuffy: я не совсем понимаю, почему вы направляете свой комментарий на mgarciaisaia. Ваши пункты кажутся очень ортогональными его. (foo $bar против foo "$bar" - это не вопрос дизайна; и в любом случае, я не думаю, что кто-то станет спорить с тем, что первый легче понять: он может выглядеть немного проще, но то, что он делает, не совсем то, чем кажется. делает.) - person ruakh; 27.02.2014
comment
@ruakh К сожалению, многие люди выдвигают именно такой аргумент. Я один из завсегдатаев канала freenode #bash, и борьба с ним происходит почти каждый день. - person Charles Duffy; 28.02.2014

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

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

person John B    schedule 24.02.2014