Позвольте мне привести пример:
$ timeout 1 yes "GNU" > file1
$ wc -l file1
11504640 file1
$ for ((sec0=`date +%S`;sec<=$(($sec0+5));sec=`date +%S`)); do echo "GNU" >> file2; done
$ wc -l file2
1953 file2
Здесь вы можете видеть, что команда yes
записывает 11504640
строки в секунду, а я могу писать только 1953
строки за 5 секунд, используя bash for
и echo
.
Как предложено в комментариях, есть несколько хитростей, чтобы сделать его более эффективным, но ни один из них не приблизился к скорости yes
:
$ ( while :; do echo "GNU" >> file3; done) & pid=$! ; sleep 1 ; kill $pid
[1] 3054
$ wc -l file3
19596 file3
$ timeout 1 bash -c 'while true; do echo "GNU" >> file4; done'
$ wc -l file4
18912 file4
Они могут записывать до 20 тысяч строк в секунду. И они могут быть улучшены до:
$ timeout 1 bash -c 'while true; do echo "GNU"; done >> file5'
$ wc -l file5
34517 file5
$ ( while :; do echo "GNU"; done >> file6 ) & pid=$! ; sleep 1 ; kill $pid
[1] 5690
$ wc -l file6
40961 file6
Это дает нам до 40 тысяч строк в секунду. Лучше, но все же далеко, из yes
которого можно написать около 11 миллионов строк в секунду!
Итак, как yes
записать в файл так быстро?
date
он несколько тяжелый, плюс оболочка должна повторно открывать выходной потокecho
для каждой итерации цикла. В первом примере есть только один вызов команды с одним перенаправлением вывода, и команда является чрезвычайно легкой. Два ни в коем случае не сопоставимы.date
может быть, тяжеловес, см. Правку на мой вопрос.timeout 1 $(while true; do echo "GNU">>file2; done;)
это неправильный способ использования,timeout
так какtimeout
команда будет запускаться только после завершения подстановки команды. Использованиеtimeout 1 sh -c 'while true; do echo "GNU">>file2; done'
.write(2)
системные вызовы, а не на загрузку других системных вызовов, издержки оболочки или даже создание процесса в самом первом примере (который запускается и ожидаетdate
каждой строки, напечатанной в файле). Одной секунды записи едва хватает для узкого места на дисковых операциях ввода-вывода (а не на процессоре / памяти) в современной системе с большим объемом оперативной памяти. Если разрешить работать дольше, разница будет меньше. (В зависимости от того, насколько плохую реализацию bash вы используете, и относительной скорости процессора и диска, вы можете даже не насыщать дисковый ввод-вывод bash).Ответы:
скорлупа:
yes
демонстрирует поведение, аналогичное большинству других стандартных утилит, которые обычно записывают в FILE STREAM с выводом, буферизованным libC через stdio . Они только делают системный вызовwrite()
каждые 4 КБ (16 КБ или 64 КБ) или независимо от выходного блока BUFSIZ .echo
этоwrite()
перGNU
. Это много в режиме коммутации (который не является, по- видимому, столь же дорогостоящим , как контекстно-переключатель ) .И это вовсе не
yes
значит, что, помимо начального цикла оптимизации, это очень простой, крошечный, скомпилированный цикл C, и ваш цикл оболочки ни в коей мере не сравним с программой, оптимизированной для компилятора.но я был неправ:
Когда я говорил, что
yes
раньше использовал stdio, я предполагал, что это так, потому что он во многом похож на те, которые используют. Это было не правильно - это только имитирует их поведение таким образом. То, что он на самом деле делает, очень похоже на то, что я сделал ниже с оболочкой: сначала он зацикливается, чтобы сопоставить свои аргументы (или,y
если их нет), пока они не могут больше расти, не превышаяBUFSIZ
.Комментарий от источника, непосредственно предшествующего соответствующим
for
состояниям цикла:yes
после этого делает свой собственныйwrite()
.отступление:
(Первоначально включено в вопрос и сохранено для контекста в возможно информативном объяснении, уже написанном здесь) :
timeout
Проблема у вас есть с заменой команды - Я думаю , что я получаю сейчас, и могу объяснить , почему он не останавливается.timeout
не запускается, потому что его командная строка никогда не запускается. Ваша оболочка разветвляется на дочернюю оболочку, открывает канал на своем стандартном выводе и читает его. Он прекратит чтение, когда ребенок уйдет, а затем интерпретирует все, что написал ребенок для$IFS
искажений и глобальных расширений, и с результатами он заменит все, начиная$(
с сопоставления)
.Но если дочерний элемент представляет собой бесконечный цикл, который никогда не записывает в канал, дочерний элемент никогда не прекращает цикл, и
timeout
командная строка никогда не завершается до того, как (как я полагаю) вы это делаетеCTRL-C
и уничтожаете дочерний цикл. Так что никогда неtimeout
можете убить цикл, который необходимо завершить, прежде чем он может начаться.другие
timeout
с:... просто не так важны для вашей производительности, как количество времени, которое ваша оболочка должна тратить на переключение между режимами пользователя и ядра для обработки вывода.
timeout
Тем не менее, он не так гибок, как оболочка для этой цели: где оболочка превосходит свои способности манипулировать аргументами и управлять другими процессами.Как отмечалось в другом месте, простое перемещение вашего
[fd-num] >> named_file
перенаправления на выходную цель цикла, а не только направление вывода туда для зацикленной команды, может существенно улучшить производительность, потому что таким образом, по крайней мере,open()
системный вызов должен быть выполнен только один раз. Это также делается ниже с помощью|
трубы, предназначенной для вывода на внутренние петли.прямое сравнение:
Вы могли бы сделать как:
Это что- то вроде отношения подчиненных команд, описанного ранее, но нет никакого канала, и потомок остается на заднем плане, пока не убьет родителя. В том
yes
случае, если родительский объект был фактически заменен с момента появления потомка, оболочка вызываетyes
, накладывая свой собственный процесс на новый, поэтому PID остается прежним, а его дочерний элемент-зомби все еще знает, кого убивать в конце концов.больший буфер:
Теперь давайте посмотрим, как увеличить
write()
буфер оболочки .Я выбрал это число, потому что выходные строки длиной более 1 КБ делятся на отдельные
write()
для меня. И вот снова цикл:Это в 300 раз превышает объем данных, записанных оболочкой, за то же время, что и в предыдущем тесте. Не слишком потрепанный. Но это не так
yes
.Связанный:
В соответствии с запросом, здесь есть более подробное описание, чем просто комментарии к коду о том, что делается здесь по этой ссылке .
источник
dd
это один стандартный инструмент, который определенно не использует, например, stdio. большинство других делают.open
(существующее)write
Иclose
(которое, я считаю, все еще ожидает сброса), И создает новый процесс и выполняет егоdate
для каждого цикла.wc -l
цикл сbash
для меня получает 1/5 - ый из выводаsh
контура делает -bash
управляет чуть более 100kwrites()
кdash
«s 500k.for((sec0=`date +%S`;...
временем управления и перенаправлением в цикле, а не последующими улучшениями.Лучший вопрос - почему ваша оболочка пишет файл так медленно. Любая автономная скомпилированная программа, которая ответственно использует системные вызовы записи файлов (не сбрасывая каждый символ за раз), сделает это достаточно быстро. То, что вы делаете, это написание строк на интерпретируемом языке (оболочке), и, кроме того, вы делаете много ненужных операций ввода-вывода. Что
yes
делает:Что делает ваш скрипт:
date
внешнюю команду и сохраните ее вывод (только в оригинальной версии - в пересмотренной версии вы получаете коэффициент 10, не делая этого)echo
Команда parse , распознает ее (с некоторым кодом соответствия шаблона) как встроенную оболочку, вызывает расширение параметра и все остальное в аргументе «GNU», и, наконец, записывает строку в открытый файлДорогие части: вся интерпретация чрезвычайно дорогая (bash делает очень большую предварительную обработку всех входных данных - ваша строка может потенциально содержать подстановку переменных, подстановку процессов, расширение фигурных скобок, escape-символы и т. Д.), Каждый вызов встроенной функции возможно, оператор switch с редиректом на функцию, которая имеет дело со встроенной функцией, и, что очень важно, вы открываете и закрываете файл для каждой строки вывода. Вы можете выйти
>> file
из цикла while, чтобы сделать его намного быстрее , но вы все еще на интерпретируемом языке. Вам очень повезло, чтоecho
это встроенная оболочка, а не внешняя команда, иначе ваш цикл будет включать создание нового процесса (fork & exec) на каждой отдельной итерации. Что остановило бы процесс - вы видели, насколько это дорого, когда у вас былаdate
команда в цикле.источник
Другие ответы касались основных моментов. Кстати, вы можете увеличить пропускную способность цикла while, записав в выходной файл в конце вычисления. Для сравнения:
с участием
источник