Чому відкриття файлу швидше, ніж читання змінного вмісту?


36

У 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

Використання ряду тут для збереження труби робить її трохи швидше, але все ж не так швидко, як у файлах:

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

Чому відкриття файлу швидше, ніж читання одного змісту зі змінної?


@ l0b0 Це припущення не є несправним, питання показує, як я придумав це, і відповіді пояснюють, чому це так. Тепер ваша редакція дає відповіді, не відповідаючи на заголовне запитання: вони не кажуть, чи так це.
десерт

Добре, уточнено. Оскільки заголовок був помилковим у переважній більшості випадків, просто не для певних пам'яті, відображених спеціальними файлами.
l0b0

@ l0b0 Ні, про це я тут прошу: "Я підозрював особливий стан /proc/файлів як причину, але коли я копіюю вміст у /proc/meminfoзвичайний файл і використовую, щоб результати були однаковими:" Це не особливо /proc/файли, читання звичайних файлів також швидше!
десерт

Відповіді:


47

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

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

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

 grep '^MemFree' << EOF
 $a
 EOF

Оболонка створює трубу, роздвоює процес. Дитина виконує grepзі своїм stdin як кінець зчитування, а батько записує вміст на іншому кінці труби.

Але ця обробка труб та синхронізація процесів все ж можуть бути дорожчими, ніж просто отримання даних /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 з опцією розширеного глобулу принаймні), я рекомендую цитувати його. Також зауважте, що echoне можна використовувати для виведення довільних даних (звідси моє використання printfвище).


4
У випадку, коли printfви говорите, оболонці потрібно породити два процеси, але чи не printfвбудована оболонка?
Девід Конрад

6
@DavidConrad Так, але більшість оболонок не намагаються проаналізувати трубопровід, для яких частин він міг би працювати в поточному процесі. Він просто роздвоюється і дозволяє дітям це зрозуміти. У цьому випадку батьківський процес розщеплюється двічі; дитина на лівій стороні потім бачить вбудований і виконує його; дитина з правого боку бачить grepі виконує.
чепнер

1
@DavidConrad, труба є механізмом IPC, тому в будь-якому випадку обом сторонам доведеться працювати в різних процесах. Хоча в A | B, є деякі оболонки, такі як AT&T ksh або zsh, які запускаються Bв поточному процесі оболонки, якщо це вбудована або з'єднана або функціональна команда, я не знаю жодної, яка працює Aв поточному процесі. Якщо що-небудь, для цього їм доведеться обробляти SIGPIPE комплексно, як ніби Aвін працює в дочірньому процесі, і не закінчуючи оболонку, щоб поведінка не була занадто дивною, коли вона Bвиходить рано. Набагато простіше запустити Bв батьківському процесі.
Стефан Шазелас

Баш підтримує<<<
Д. Бен Нобл

1
@ D.BenKnoble, я не мав на увазі, bashщо не підтримував <<<, просто те, що оператор, zshяк, наприклад, $(<...)прийшов з ksh.
Стефан Шазелас

6

У вашому першому випадку ви просто використовуєте 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

1

Ви викликаєте зовнішню команду в обох випадках (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 секунди для греп-дзвінка.

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