Я сравниваю следующее
tail -n 1000000 stdout.log | grep -c '"success": true'
tail -n 1000000 stdout.log | grep -c '"success": false'
со следующим
log=$(tail -n 1000000 stdout.log)
echo "$log" | grep -c '"success": true'
echo "$log" | grep -c '"success": false'
и удивительно, что второе занимает почти в 3 раза больше, чем первое. Это должно быть быстрее, не так ли?
bash
performance
io
phunehehe
источник
источник
$( command substitution )
есть не текли. Все остальное происходит по каналам одновременно, но во втором примере вы должны ждатьlog=
завершения. Попробуйте с помощью << ЗДЕСЬ \ n $ {log = $ (команда)} \ n ЗДЕСЬ - посмотрите, что вы получите.grep
, вы можете заметить некоторое ускорение,tee
так что файл определенно будет прочитан только один раз.cat stdout.log | tee >/dev/null >(grep -c 'true'>true.cnt) >(grep -c 'false'>false.cnt); cat true.cnt; cat false.cnt
tail -n 10000 | fgrep -c '"success": true'
ложным.Ответы:
С одной стороны, первый метод вызывается
tail
дважды, поэтому он должен выполнять больше работы, чем второй метод, который делает это только один раз. С другой стороны, второй метод должен копировать данные в оболочку и затем возвращаться обратно, поэтому он должен выполнять больше работы, чем первая версия,tail
в которую напрямую передаются данныеgrep
. Первый метод имеет дополнительное преимущество на многопроцессорной машине: онgrep
может работать параллельноtail
, тогда как второй метод сначала строго сериализуетсяtail
, а затемgrep
.Так что нет очевидной причины, почему один должен быть быстрее другого.
Если вы хотите увидеть, что происходит, посмотрите, что система вызывает из оболочки. Попробуйте тоже с разными оболочками.
При использовании способа 1 основными этапами являются:
tail
читает и стремится найти его отправную точку.tail
записывает 4096-байтовые чанки, которыеgrep
читаются так же быстро, как и создаются.С помощью метода 2 основными этапами являются:
tail
читает и стремится найти его отправную точку.tail
записывает 4096-байтовые чанки, которые bash читает 128 байтов за раз, а zsh читает 4096 байтов за раз.grep
читаются так же быстро, как и создаются.128-байтовые чанки Bash при чтении вывода команды подстановки значительно замедляют его; zsh выходит так же быстро, как метод 1 для меня. Ваш пробег может варьироваться в зависимости от типа и количества ЦП, конфигурации планировщика, версий используемых инструментов и размера данных.
источник
st_blksize
значение для канала, которое на этом компьютере равно 4096 (и я не знаю, так ли это, потому что это размер страницы MMU). 128 Bash должен быть встроенной константой.Я провел следующий тест, и в моей системе результирующая разница для второго сценария примерно в 100 раз больше.
Мой файл является выводом strace, который называется
bigfile
Сценарии
На самом деле у меня нет совпадений для grep, поэтому ничего не записывается в последний канал до
wc -l
Вот время:
Итак, я снова запустил два скрипта с помощью команды strace
Вот результаты из следов:
И p2.strace
Анализ
Неудивительно, что в обоих случаях большая часть времени тратится на ожидание завершения процесса, но 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.источник
На самом деле первое решение также считывает файл в память! Это называется кэшированием и автоматически выполняется операционной системой.
И, как уже правильно объяснил mikeserv, первое решение работает
grep
во время чтения файла, тогда как второе решение выполняет его после того, как файл был прочитанtail
.Поэтому первое решение быстрее из-за различных оптимизаций. Но это не всегда должно быть правдой. Для действительно больших файлов, которые ОС решает не кэшировать, второе решение может стать быстрее. Но обратите внимание, что для еще больших файлов, которые не помещаются в вашей памяти, второе решение не будет работать вообще.
источник
Я думаю, что основное отличие очень простое
echo
- медленное. Учти это:Как вы можете видеть выше, трудоемким шагом является печать данных. Если вы просто перенаправляете на новый файл и просматриваете его, это происходит намного быстрее, когда вы читаете файл только один раз.
И в соответствии с просьбой, здесь строка:
Это еще медленнее, предположительно потому, что строка здесь объединяет все данные в одну длинную строку, что замедляет
grep
:Если переменная заключена в кавычки, чтобы не происходило разбиение, все происходит немного быстрее:
Но все еще медленно, потому что шаг ограничения скорости печатает данные.
источник
<<<
было бы интересно посмотреть, если это имеет значение.Я тоже попробовал это ... Сначала я собрал файл:
Если вы запустите вышеописанное, вы должны получить 1,5 миллиона строк
/tmp/log
с соотношением"success": "true"
строк : 2: 1"success": "false"
.Следующее, что я сделал, - запустил несколько тестов. Я выполнил все тесты через прокси-сервер,
sh
поэтому мнеtime
нужно было наблюдать только один процесс - и, следовательно, мог показать один результат для всей работы.Это кажется самым быстрым, хотя он добавляет второй дескриптор файла и
tee,
хотя я думаю, что могу объяснить, почему:Вот твой первый:
И твой второй:
Вы можете видеть, что в моих тестах скорость чтения была более чем в 3 * раза при чтении в переменную, как вы это делали.
Я думаю, что отчасти это то, что переменная оболочки должна быть разделена и обработана оболочкой во время чтения - это не файл.
here-document
С другой стороны, для всех намерений и целей, этоfile
- этоfile descriptor,
в любом случае. И как мы все знаем - Unix работает с файлами.Что самое интересное для меня в
here-docs
том, что вы можете манипулировать ихfile-descriptors
- как прямой|pipe
- и выполнять их. Это очень удобно, так как дает вам немного больше свободы, указывая,|pipe
куда вы хотите.Я должен был , потому что первые съедает и там ничего не осталось за секунду для чтения. Но так как я его в и поднял его снова , чтобы перейти к не имело большого значения. Если вы используете так много других, рекомендуем:
tee
tail
grep
here-doc |pipe
|piped
/dev/fd/3
>&1 stdout,
grep -c
Это даже быстрее.
Но когда я запускаю его без
. sourcing
вheredoc
I не может успешно фон первый процесс , чтобы запустить их в полном объеме одновременно. Вот это без полного фона:Но когда я добавляю
&:
Тем не менее, разница, похоже, составляет всего несколько сотых долей секунды, по крайней мере для меня, поэтому примите это как хотите.
В любом случае, причина, по которой он работает быстрее,
tee
заключается в том, что обаgreps
запускаются одновременно с одним вызовом, которыйtail. tee
дублирует для нас файл и разделяет его со вторымgrep
процессом в потоке - все запускается сразу от начала до конца, поэтому они все заканчивают примерно в одно и то же время.Итак, возвращаясь к вашему первому примеру:
И твой второй:
Но когда мы разделяем наш ввод и запускаем наши процессы одновременно:
источник