В bash
скрипте мне нужны различные значения из /proc/
файлов. До сих пор у меня есть десятки строк, которые напрямую копируют файлы:
grep -oP '^MemFree: *\K[0-9]+' /proc/meminfo
Чтобы сделать это более эффективным, я сохранил содержимое файла в переменной и добавил следующее:
a=$(</proc/meminfo)
echo "$a" | grep -oP '^MemFree: *\K[0-9]+'
Вместо того, чтобы открывать файл несколько раз, нужно просто открыть его один раз и выполнить поиск содержимого переменной, что, как я предполагал, будет быстрее, но на самом деле это медленнее:
bash 4.4.19 $ time for i in {1..1000};do grep ^MemFree /proc/meminfo;done >/dev/null
real 0m0.803s
user 0m0.619s
sys 0m0.232s
bash 4.4.19 $ a=$(</proc/meminfo)
bash 4.4.19 $ time for i in {1..1000};do echo "$a"|grep ^MemFree; done >/dev/null
real 0m1.182s
user 0m1.425s
sys 0m0.506s
То же самое относится и к dash
и zsh
. Я подозревал особое состояние /proc/
файлов как причину, но когда я копирую содержимое /proc/meminfo
в обычный файл и использую, результаты совпадают:
bash 4.4.19 $ cat </proc/meminfo >meminfo
bash 4.4.19 $ time for i in $(seq 1 1000);do grep ^MemFree meminfo; done >/dev/null
real 0m0.790s
user 0m0.608s
sys 0m0.227s
Использование строки here для сохранения канала делает его немного быстрее, но все же не так быстро, как с файлами:
bash 4.4.19 $ time for i in $(seq 1 1000);do <<<"$a" grep ^MemFree; done >/dev/null
real 0m0.977s
user 0m0.758s
sys 0m0.268s
Почему открытие файла происходит быстрее, чем чтение того же содержимого из переменной?
/proc/
файлов как причину, но когда я копирую содержимое/proc/meminfo
в обычный файл и использую, что результаты совпадают:« Это не специально для/proc/
файлы, чтение обычных файлов также быстрее!Ответы:
Здесь речь идет не об открытии файла, а о прочтении содержимого переменной, а скорее о том, чтобы создать дополнительный процесс или нет.
grep -oP '^MemFree: *\K[0-9]+' /proc/meminfo
разветвляется процесс, который запускается,grep
который открывается/proc/meminfo
(виртуальный файл, в памяти, без дискового ввода-вывода) читает его и соответствует регулярному выражению.Самая дорогая часть в этом - это разветвление процесса и загрузка утилиты grep и ее зависимостей от библиотеки, динамическое связывание, открытие базы данных локали, десятки файлов, которые находятся на диске (но, вероятно, кешируются в памяти).
Часть о чтении
/proc/meminfo
несущественна по сравнению с тем, что ядру не нужно много времени, чтобы генерировать информацию, и неgrep
нужно много времени, чтобы ее прочитать.Если вы выполните
strace -c
это, вы увидите, что одинopen()
и одинread()
системные вызовы, используемые для чтения,/proc/meminfo
- это арахис по сравнению со всем, чтоgrep
нужно для запуска (strace -c
не считая разветвления).В:
В большинстве оболочек, которые поддерживают этот
$(<...)
оператор ksh, оболочка просто открывает файл и читает его содержимое (и удаляет завершающие символы новой строки).bash
отличается и намного менее эффективен в том, что он разветвляет процесс, выполняющий это чтение, и передает данные в родительский канал по каналу. Но здесь это делается один раз, поэтому это не имеет значения.В:
Оболочка должна порождать два процесса, которые работают одновременно, но взаимодействуют друг с другом через канал. Создание этой трубы, ее разрушение, а также запись и чтение из нее имеют небольшую стоимость. Гораздо большая стоимость порождает дополнительный процесс. Планирование процессов также оказывает определенное влияние.
Вы можете обнаружить, что использование
<<<
оператора zsh делает его немного быстрее:В zsh и bash это делается путем записи содержимого
$a
во временный файл, что обходится дешевле, чем запуск дополнительного процесса, но, вероятно, не даст вам никакой выгоды по сравнению с получением данных сразу/proc/meminfo
. Это все еще менее эффективно, чем ваш подход, который копирует/proc/meminfo
на диск, так как запись временного файла выполняется на каждой итерации.dash
не поддерживает здесь-строки, но его heredocs реализованы с помощью канала, который не требует создания дополнительного процесса. В:Оболочка создает трубу, разветвляет процесс. Дочерний элемент выполняется
grep
со своим стандартным вводом-выводом в качестве конца чтения канала, а родительский элемент записывает содержимое на другом конце канала.Но такая обработка каналов и синхронизация процессов все еще могут быть более дорогостоящими, чем просто получение данных
/proc/meminfo
.Содержание
/proc/meminfo
короткое и не занимает много времени для производства. Если вы хотите сохранить некоторые циклы ЦП, вы хотите удалить дорогостоящие детали: разветвление процессов и запуск внешних команд.Подобно:
Избегайте,
bash
чье сопоставление с образцом очень неэффективно. С помощьюzsh -o extendedglob
вы можете сократить его до:Обратите внимание, что
^
это особенность во многих оболочках (Bourne, fish, rc, es и zsh, по крайней мере, с параметром extendedglob), я бы рекомендовал процитировать его. Также обратите внимание, чтоecho
не может использоваться для вывода произвольных данных (отсюда мое использованиеprintf
выше).источник
printf
вами говорят, что оболочка должна порождать два процесса, но развеprintf
оболочка не встроена?grep
и исполняет.A | B
есть некоторые оболочки, такие как AT & T ksh или zsh, которые выполняютсяB
в текущем процессе оболочки, если это встроенная, составная или функциональная команда, я не знаю ни одной, которая запускаетсяA
в текущем процессе. Во всяком случае, чтобы сделать это, им придется обрабатывать SIGPIPE сложным образом, как если быA
он выполнялся в дочернем процессе и без завершения оболочки, чтобы поведение не было слишком удивительным приB
раннем выходе. Намного легче работатьB
в родительском процессе.<<<
bash
что не поддерживает<<<
, просто что оператор пришел,zsh
как$(<...)
пришел из ksh.В первом случае вы просто используете утилиту grep и находите что-то из файла
/proc/meminfo
,/proc
это виртуальная файловая система, поэтому/proc/meminfo
файл находится в памяти, и для его извлечения требуется очень мало времени.Но во втором случае вы создаете канал, а затем передаете вывод первой команды второй команде, используя этот канал, что является дорогостоящим.
Разница заключается в
/proc
(потому что это в памяти) и трубе, см. Пример ниже:источник
Вы вызываете внешнюю команду в обоих случаях (grep). Внешний вызов требует подоболочки. Создание этой оболочки является основной причиной задержки. Оба случая одинаковы, поэтому: одинаковая задержка.
Если вы хотите прочитать внешний файл только один раз и использовать его (из переменной) несколько раз, не выходите из оболочки:
Что занимает всего около 0,1 секунды вместо полных 1 секунды для вызова grep.
источник