Я провел следующий тест, и в моей системе результирующая разница для второго сценария примерно в 100 раз больше.
Мой файл является выводом strace, который называется 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
На самом деле у меня нет совпадений для grep, поэтому ничего не записывается в последний канал до 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 составляет 705 тыс. против 84 тыс. для p1, при каждом чтении требуется переключение контекста в пространство ядра и обратно. Почти в 10 раз превышает число операций чтения и переключения контекста.
Время записи p2 тратит в 41,93 раза больше времени записи, чем p1
количество записей p1 делает больше записей, чем p2, 42k против 21k, однако они намного быстрее.
Вероятно, из- echo
за строк в grep
отличие от хвостовых буферов записи.
Более того , p2 тратит больше времени на запись, чем на чтение, p1 наоборот!
Другой фактор Посмотрите на количество brk
системных вызовов: p2 тратит в 2,42 раза больше времени, чем на чтение! В p1 (он даже не регистрируется). brk
это когда программе нужно расширить свое адресное пространство, потому что изначально не было выделено достаточно места, это, вероятно, связано с тем, что bash нужно прочитать этот файл в переменную, а не ожидать, что он будет таким большим, и, как упомянул @scai, если файл становится слишком большим, даже это не будет работать.
tail
это, вероятно, довольно эффективный файл-ридер, потому что это то, для чего он предназначен, он, вероятно, запоминает файл и просматривает разрывы строк, что позволяет ядру оптимизировать ввод-вывод. bash не так хорош как по времени, потраченному на чтение и письмо.
p2 тратит 44 мс и 41 мс, clone
и execv
это не измеримая величина для p1. Вероятно, чтение bash и создание переменной из tail.
В итоге Totals p1 выполняет ~ 150 тыс. Системных вызовов против p2 740 тыс. (В 4,93 раза больше).
Устраняя waitpid, p1 тратит 0,014416 секунды на выполнение системных вызовов, p2 0,439132 секунды (в 30 раз дольше).
Похоже, что p2 проводит большую часть времени в пользовательском пространстве, ничего не делая, кроме ожидания завершения системных вызовов и перестройки памяти ядром, p1 выполняет больше операций записи, но более эффективно и вызывает значительно меньшую нагрузку на систему и, следовательно, быстрее.
Вывод
Я никогда не стал бы беспокоиться о кодировании через память при написании сценария bash, это не значит, что вы не пытаетесь работать эффективно.
tail
предназначен для того, чтобы делать то, что он делает, это, вероятно, memory maps
файл, чтобы его было удобно читать, и он позволяет ядру оптимизировать ввод / вывод.
Лучшим способом оптимизации вашей проблемы может быть сначала grep
«строки успеха»: «, а затем подсчитывать истинные и ложные значения», grep
есть опция подсчета, которая снова позволяет избежать wc -l
или, что еще лучше, направить хвост к подсчету истинных чисел awk
и подсчитать их. ложится одновременно p2 не только занимает много времени, но и добавляет нагрузку на систему, пока память перемешивается с brks.