Я зробив наступний тест, і в моїй системі отримана різниця приблизно в 100 разів довша для другого сценарію.
Мій файл - це виклик страйку bigfile
$ wc -l bigfile.log
1617000 bigfile.log
Сценарії
xtian@clafujiu:~/tmp$ cat p1.sh
tail -n 1000000 bigfile.log | grep '"success": true' | wc -l
tail -n 1000000 bigfile.log | grep '"success": false' | wc -l
xtian@clafujiu:~/tmp$ cat p2.sh
log=$(tail -n 1000000 bigfile.log)
echo "$log" | grep '"success": true' | wc -l
echo "$log" | grep '"success": true' | wc -l
Насправді у мене немає жодних збігів на греп, тому нічого не записується в останню трубку через wc -l
Ось терміни:
xtian@clafujiu:~/tmp$ time bash p1.sh
0
0
real 0m0.381s
user 0m0.248s
sys 0m0.280s
xtian@clafujiu:~/tmp$ time bash p2.sh
0
0
real 0m46.060s
user 0m43.903s
sys 0m2.176s
Тому я запустив два сценарії знову за допомогою команди strace
strace -cfo p1.strace bash p1.sh
strace -cfo p2.strace bash p2.sh
Ось результати від слідів:
$ cat p1.strace
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
97.24 0.508109 63514 8 2 waitpid
1.61 0.008388 0 84569 read
1.08 0.005659 0 42448 write
0.06 0.000328 0 21233 _llseek
0.00 0.000024 0 204 146 stat64
0.00 0.000017 0 137 fstat64
0.00 0.000000 0 283 149 open
0.00 0.000000 0 180 8 close
...
0.00 0.000000 0 162 mmap2
0.00 0.000000 0 29 getuid32
0.00 0.000000 0 29 getgid32
0.00 0.000000 0 29 geteuid32
0.00 0.000000 0 29 getegid32
0.00 0.000000 0 3 1 fcntl64
0.00 0.000000 0 7 set_thread_area
------ ----------- ----------- --------- --------- ----------------
100.00 0.522525 149618 332 total
І p2.strace
$ cat p2.strace
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
75.27 1.336886 133689 10 3 waitpid
13.36 0.237266 11 21231 write
4.65 0.082527 1115 74 brk
2.48 0.044000 7333 6 execve
2.31 0.040998 5857 7 clone
1.91 0.033965 0 705681 read
0.02 0.000376 0 10619 _llseek
0.00 0.000000 0 248 132 open
...
0.00 0.000000 0 141 mmap2
0.00 0.000000 0 176 126 stat64
0.00 0.000000 0 118 fstat64
0.00 0.000000 0 25 getuid32
0.00 0.000000 0 25 getgid32
0.00 0.000000 0 25 geteuid32
0.00 0.000000 0 25 getegid32
0.00 0.000000 0 3 1 fcntl64
0.00 0.000000 0 6 set_thread_area
------ ----------- ----------- --------- --------- ----------------
100.00 1.776018 738827 293 total
Аналіз
Не дивно, що в обох випадках більша частина часу витрачається на очікування завершення процесу, але p2 чекає в 2,63 рази довше, ніж p1, і, як уже згадували інші, ви починаєте пізно в p2.sh.
Тому тепер забудьте про waitpid
, ігноруйте %
стовпчик і подивіться на стовпці секунд на обох слідах.
Найбільший час p1 проводить більшу частину свого часу на читання, ймовірно, зрозуміло, тому що для читання є великий файл, але p2 витрачається на 28,82 рази довше, ніж p1. - bash
не сподівається прочитати такий великий файл у змінній і, ймовірно, читає буфер за раз, розбиваючись на рядки, а потім отримуючи інший.
кількість читання p2 становить 705k проти 84k для p1, кожне читання вимагає переключення контексту на простір ядра та знову. Майже в 10 разів перевищує кількість зчитувань та перемикань контексту.
Час запису p2 витрачає на запис у 41,93 рази більше, ніж p1
кількість записів p1 робить більше записів, ніж p2, 42k проти 21k, проте вони набагато швидші.
Можливо, через echo
рядки в, grep
а не в буфери для написання хвоста.
Крім того , p2 витрачає більше часу на запис, ніж у читанні, p1 - навпаки!
Інший фактор Подивіться на кількість brk
системних дзвінків: p2 витрачає 2,42 рази більше часу, ніж читання! У p1 (він навіть не реєструється). brk
коли програма повинна розширити свій адресний простір, оскільки спочатку не було виділено достатньо місця, це, ймовірно, пов’язано з тим, що баш повинен прочитати цей файл у змінній, і не очікуючи, що він буде таким великим, і як згадував @scai, якщо файл стає занадто великим, навіть це не працює.
tail
це, мабуть, досить ефективний зчитувач файлів, тому що це те, що він був розроблений, він, ймовірно, запам’ятовує файл і сканує розриви рядків, таким чином, дозволяючи ядру оптимізувати введення-виведення. bash не так добре, як на час, витрачений на читання та письмо.
p2 витрачає 44 мс і 41 мс, clone
і execv
це не вимірюється кількість для p1. Можливо, читання башти та створення змінної від хвоста.
Нарешті Totals p1 виконує ~ 150k системних викликів проти p2 740k (у 4,93 рази більше).
Усуваючи чеканку, p1 витрачає 0,014416 секунд, виконуючи системні виклики, p2 0,439132 секунди (у 30 разів довше).
Таким чином, схоже, p2 проводить більшу частину часу в просторі користувача, не роблячи нічого, крім очікування завершення системних викликів і ядра для реорганізації пам'яті, p1 виконує більше записів, але є більш ефективним і викликає значно меншу завантаженість системи, а значить, швидше.
Висновок
Я б ніколи не намагався турбуватися про кодування через пам'ять під час написання bash-сценарію, це не означає сказати, що ви не намагаєтесь бути ефективними.
tail
призначений для того, щоб робити те, що він робить, він, ймовірно, memory maps
файл, щоб він був ефективним для читання і дозволяє ядру оптимізувати введення / виведення.
Кращим способом оптимізувати вашу проблему може бути спочатку grep
для рядків "" успіху ":", а потім підраховувати підрахунки і помилки, grep
є варіант підрахунку, який знову дозволяє уникнути wc -l
, а ще краще, пропустити хвіст до awk
і підрахувати підрахунки і помилково одночасно. p2 не тільки займає багато часу, але додає навантаження на систему, в той час як пам'ять перетасовується за допомогою brks.