Почему открытие файла происходит быстрее, чем чтение переменного содержимого?

Я нашел эти примеры на SO в этих вопросах и ответах под названием: Как выполнить цикл for для каждого символа в строке в BASH? .

Пример №1 - деструктивный

Вот сценарий под названием onechar1.bash .

#!/bin/bash

echo "Please enter a word:
(leave blank and press Enter to exit)"
read someStr

if [[ $someStr == '' ]]; then
    exit
fi

while test -n "$someStr"; do
   c=${someStr:0:1}       # Get the first character
   echo "character is $c"
   someStr=${someStr:1}   # trim the first character
done

Когда я запускаю его:

$ ./onechar1.bash 
Please enter a word:
(leave blank and press Enter to exit)
My Name is Mauro
character is M
character is y
character is  
character is N
character is a
character is m
character is e
character is  
character is i
character is s
character is  
character is M
character is a
character is u
character is r
character is o
$

Пример № 2 - неразрушающий

Вот еще один сценарий, onechar2.bash :

#!/bin/bash

echo "Please enter a word:
(leave blank and press Enter to exit)"
read someStr

if [[ $someStr == '' ]]; then
    exit
fi

for (( i=0; i<${#someStr}; i++ )); do
    echo ${someStr:$i:1}
done

Когда я запускаю этот сценарий:

$ ./onechar2.bash 
Please enter a word:
(leave blank and press Enter to exit)
My Name is Mauro
M
y

N
a
m
e

i
s

M
a
u
r
o
$

36
21.02.2019, 21:35
3 ответа

Здесь речь идет не об открытии файла и чтении содержимого переменной , а о разветвлении дополнительного процесса или нет.

grep -oP '^MemFree: *\K[0-9]+' /proc/meminfoразветвляет процесс, который выполняет grep, который открывает/proc/meminfo(виртуальный файл в памяти, без участия дискового ввода-вывода )читает его и сопоставляет регулярное выражение.

Самая затратная часть этого процесса — разветвление процесса и загрузка утилиты grep и ее библиотечных зависимостей, выполнение динамического связывания, открытие базы данных локали, десятки файлов, которые находятся на диске (, но, вероятно, кэшируются в памяти ).

Часть о чтении /proc/meminfoнезначительна по сравнению с ней, ядру требуется мало времени для генерации информации, а grepтребуется мало времени для ее чтения.

Если вы запустите strace -cна этом, вы увидите, что один open()и один read()системные вызовы, используемые для чтения /proc/meminfo, являются арахисом по сравнению со всем остальным, что grepделает для запуска(strace -cнет развилки не считай ).

В:

a=$(</proc/meminfo)

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

В:

printf '%s\n' "$a" | grep '^MemFree'

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

Вы можете обнаружить, что использование оператора zsh <<<делает это немного быстрее:

grep '^MemFree' <<< "$a"

В zsh и bash это делается путем записи содержимого $aво временный файл, что дешевле, чем создание дополнительного процесса, но, вероятно, не даст вам никаких преимуществ по сравнению с получением данных сразу /proc/meminfo. Это все еще менее эффективно, чем ваш подход, который копирует /proc/meminfoна диск, поскольку запись временного файла выполняется на каждой итерации.

dashне поддерживает здесь -строки, но его heredocs реализованы с конвейером, который не включает порождение дополнительного процесса. В:

 grep '^MemFree' << EOF
 $a
 EOF

Оболочка создает канал, разветвляет процесс. Дочерний процесс выполняет grepсо своим стандартным вводом в качестве конца канала для чтения, а родитель записывает содержимое на другом конце канала.

Но обработка каналов и синхронизация процессов, вероятно, по-прежнему обходятся дороже, чем простое получение данных /proc/meminfo.

Содержание /proc/meminfoкороткое, и его создание не занимает много времени. Если вы хотите сэкономить несколько циклов ЦП, вы хотите удалить дорогостоящие части :разветвления процессов и выполнения внешних команд.

Нравится:

IFS= read -rd '' meminfo < /proc/meminfo
memfree=${meminfo#*MemFree:}
memfree=${memfree%%$'\n'*}
memfree=${memfree#"${memfree%%[! ]*}"}

Избегайте bashхотя сопоставление с образцом очень неэффективно. С помощью zsh -o extendedglobвы можете сократить его до :

.
memfree=${${"$(</proc/meminfo)"##*MemFree: #}%%$'\n'*}

Обратите внимание, что ^является особенным во многих оболочках (Bourne, fish, rc, es и zsh с параметром extendedglob по крайней мере ), я бы рекомендовал его процитировать.Также обратите внимание, что echoнельзя использовать для вывода произвольных данных (, поэтому я использую printfвыше ).

48
27.01.2020, 19:36

Вы вызываете внешнюю команду в обоих случаях (grep ). Для внешнего вызова требуется подоболочка. Разветвление этой оболочки является основной причиной задержки. Оба случая похожи, поэтому :одинаковая задержка.

Если вы хотите прочитать внешний файл только один раз и использовать его (из переменной )несколько раз, не выходите из оболочки:

meminfo=$(< /dev/meminfo)    
time for i in {1..1000};do 
    [[ $meminfo =~ MemFree:\ *([0-9]*)\ *.B ]] 
    printf '%s\n' "${BASH_REMATCH[1]}"
done

Это занимает всего около 0,1 секунды вместо полной 1 секунды для вызова grep.

1
27.01.2020, 19:36

В вашем первом случае вы просто используете утилиту grep и находите что-то в файле /proc/meminfo, /proc— это виртуальная файловая система, поэтому файл /proc/meminfoнаходится в памяти, и для извлечения его содержимого требуется очень мало времени..

Но во втором случае вы создаете канал, а затем передаете вывод первой команды второй команде, используя этот канал, что дорого.

Разница в том, что/proc(находится в памяти )и канале, см. пример ниже:

time for i in {1..1000};do grep ^MemFree /proc/meminfo;done >/dev/null

real    0m0.914s
user    0m0.032s
sys     0m0.148s


cat /proc/meminfo > file
time for i in {1..1000};do grep ^MemFree file;done >/dev/null

real    0m0.938s
user    0m0.032s
sys     0m0.152s


time for i in {1..1000};do echo "$a"|grep ^MemFree; done >/dev/null

real    0m1.016s
user    0m0.040s
sys     0m0.232s
6
27.01.2020, 19:36

Теги

Похожие вопросы