Ответ на этот вопрос заставил меня задать еще один вопрос:
я думал, что следующие скрипты делают то же самое, а второй должен быть намного быстрее, потому что первый использует cat
тот, который должен открывать файл снова и снова, а второй открывает только файл один раз, а затем просто выводит переменную:
(См. Раздел обновления для правильного кода.)
Первый:
#!/bin/sh
for j in seq 10; do
cat input
done >> output
Во-вторых:
#!/bin/sh
i=`cat input`
for j in seq 10; do
echo $i
done >> output
в то время как ввод составляет около 50 мегабайт.
Но когда я попробовал второй, он тоже был слишком медленным, потому что вывод переменной i
был огромным процессом. У меня также возникли некоторые проблемы со вторым скриптом, например, размер выходного файла оказался меньше ожидаемого.
Я также проверил справочную страницу echo
и cat
сравнил их:
echo - отображать строку текста
cat - объединяет файлы и печатает на стандартный вывод
Но я не понял разницу.
Так:
- Почему кошка так быстро, а эхо так медленно во втором сценарии?
- Или проблема с переменной
i
? (потому что в справочной страницеecho
сказано, что она отображает «строку текста», и поэтому я думаю, что она оптимизирована только для коротких переменных, а не для очень очень длинных переменных, таких какi
. Однако это только предположение.) - И почему у меня проблемы при использовании
echo
?
ОБНОВИТЬ
Я использовал seq 10
вместо `seq 10`
неправильно. Это отредактированный код:
Первый:
#!/bin/sh
for j in `seq 10`; do
cat input
done >> output
Во-вторых:
#!/bin/sh
i=`cat input`
for j in `seq 10`; do
echo $i
done >> output
(Особая благодарность roaima .)
Однако проблема не в этом. Даже если цикл происходит только один раз, я получаю ту же проблему: cat
работает намного быстрее, чем echo
.
cat $(for i in $(seq 1 10); do echo "input"; done) >> output
? :)echo
быстрее. Вам не хватает того, что вы заставляете оболочку выполнять слишком много работы, не заключая в кавычки переменные при их использовании.printf '%s' "$i"
, а неecho $i
. @cuonglm хорошо объясняет некоторые проблемы эха в своем ответе. Почему даже в некоторых случаях с эхо недостаточно цитировать, см. Unix.stackexchange.com/questions/65803/…Ответы:
Здесь есть несколько вещей, которые следует учитывать.
может быть дорогим, и есть много вариантов между оболочками.
Эта функция называется подстановкой команд. Идея состоит в том, чтобы сохранить весь вывод команды за вычетом символов новой строки в
i
переменной в памяти.Для этого оболочка раскладывает команду в подоболочку и считывает ее вывод через канал или пару сокетов. Вы видите много вариантов здесь. Например, в файле размером 50 МБ, например, bash работает в 6 раз медленнее, чем ksh93, но немного быстрее zsh и в два раза быстрее
yash
.Основная причина
bash
замедления заключается в том, что он читает из канала 128 байтов за раз (в то время как другие оболочки читают по 4 КБ или 8 КБ за раз) и подвергается штрафным расходам из-за системных вызовов.zsh
необходимо выполнить некоторую постобработку, чтобы избежать байтов NUL (другие оболочки разбиваются на байты NUL), иyash
выполняет еще более тяжелую обработку, анализируя многобайтовые символы.Всем оболочкам нужно убрать завершающие символы новой строки, которые они могут делать более или менее эффективно.
Некоторые могут захотеть обрабатывать байты NUL более изящно, чем другие, и проверять их наличие.
Затем, когда у вас есть эта большая переменная в памяти, любая манипуляция с ней обычно включает выделение дополнительной памяти и копирование данных.
Здесь вы передаете (намеревались передать) содержимое переменной
echo
.К счастью,
echo
он встроен в вашу оболочку, в противном случае выполнение, скорее всего, завершилось бы неудачей со слишком длинной ошибкой списка аргументов . Даже в этом случае создание массива списка аргументов может потребовать копирования содержимого переменной.Другая основная проблема в вашем подходе подстановки команд заключается в том, что вы вызываете оператор split + glob (забывая процитировать переменную в кавычках).
Для этого оболочкам необходимо обрабатывать строку как строку символов (хотя некоторые оболочки этого не делают и содержат ошибки), поэтому в локалях UTF-8 это означает синтаксический анализ последовательностей UTF-8 (если это еще не сделано, как в
yash
случае с) ищите$IFS
символы в строке. Если он$IFS
содержит пробел, символ табуляции или новую строку (по умолчанию), алгоритм еще более сложный и дорогой. Затем слова, полученные в результате этого разделения, должны быть выделены и скопированы.Часть шара будет еще дороже. Если любые из этих слов содержат Глобы символы (
*
,?
,[
), то оболочка будет читать содержимое некоторых каталогов и сделать некоторый дорогой по шаблону (bash
реализация «s, например , как известно , очень плохо в этом).Если вход содержит что-то вроде этого
/*/*/*/../../../*/*/*/../../../*/*/*
, это будет очень дорого, поскольку это означает перечисление тысяч каталогов, и это может увеличиться до нескольких сотен МБ.Затем
echo
обычно делают дополнительную обработку. Некоторые реализации расширяют\x
последовательности в полученном аргументе, что означает анализ содержимого и, возможно, другое выделение и копирование данных.С другой стороны, OK, в большинстве оболочек
cat
не является встроенным, так что это означает, что процесс должен быть разветвлен и выполнен (загрузка кода и библиотек), но после первого вызова этот код и содержимое входного файла будет кешироваться в памяти. С другой стороны, посредника не будет.cat
будет читать большое количество за раз и записывать его сразу же без обработки, и ему не нужно выделять огромное количество памяти, только тот буфер, который он использует повторно.Это также означает, что он намного более надежен, так как не душит байты NUL и не обрезает завершающие символы новой строки (и не использует split + glob, хотя вы можете избежать этого, заключив переменную в кавычки, и не расширить escape-последовательность, хотя вы можете избежать этого, используя
printf
вместоecho
).Если вы хотите оптимизировать его дальше, вместо
cat
нескольких вызовов , просто перейдитеinput
несколько раз кcat
.Будет работать 3 команды вместо 100.
Чтобы сделать версию переменной более надежной, вам нужно использовать
zsh
(другие оболочки не могут справиться с байтами NUL) и сделать это:Если вы знаете, что входные данные не содержат байтов NUL, то вы можете надежно сделать это POSIXly (хотя он может не работать там, где
printf
не встроено) с помощью:Но это никогда не будет более эффективным, чем использование
cat
в цикле (если вход не очень маленький).источник
/bin/echo $(perl -e 'print "A"x999999')
dd bs=128 < input > /dev/null
сdd bs=64 < input > /dev/null
. Из 0,6 с, необходимых для bash, чтобы прочитать этот файл, 0,4 тратятся на этиread
системные вызовы в моих тестах, в то время как другие оболочки проводят там гораздо меньше времени.readwc()
иtrim()
в Burne Shell занимают 30% всего времени, и это, скорее всего, недооценено, поскольку нет libc сgprof
аннотацией дляmbtowc()
.\x
распространяется?Проблема не в том,
cat
аecho
в переменной забытой кавычки$i
.В Bourne-подобном сценарии оболочки (кроме
zsh
), оставляя переменные без кавычек, вызывайтеglob+split
операторы переменных.на самом деле:
Таким образом, с каждой итерацией цикла все содержимое
input
(исключая завершающие символы новой строки) будет расширено, разделено, выделено. Весь процесс требует оболочки для выделения памяти, разбирая строку снова и снова. Вот почему у вас плохая производительность.Вы можете заключить переменную в кавычки, чтобы предотвратить,
glob+split
но это вам не очень поможет, так как, когда оболочке все еще нужно создать аргумент большой строки и проверить его содержимоеecho
(замена встроенной функцииecho
external/bin/echo
даст вам список аргументов слишком длинный или нехватки памяти зависит от$i
размера). Большая частьecho
реализации не совместима с POSIX, она расширит\x
последовательности обратной косой черты в полученных аргументах.При
cat
этом оболочке нужно только порождать процесс на каждой итерации цикла иcat
выполнять копирование ввода-вывода. Система также может кэшировать содержимое файла, чтобы ускорить процесс cat.источник
/*/*/*/*../../../../*/*/*/*/../../../../
может быть в содержимом файла. Просто хочу указать на детали .time echo $( <xdditg106) >/dev/null real 0m0.125s user 0m0.085s sys 0m0.025s
time echo "$( <xdditg106)" >/dev/null real 0m0.047s user 0m0.016s sys 0m0.022s
glob+split
часть, и это ускорит цикл while. И я также отметил, что это вам не сильно поможет. С тех пор, когда большая частьecho
поведения оболочки не соответствует POSIX.printf '%s' "$i"
лучше.Если вы позвоните
это позволяет вашему процессу оболочки увеличиваться на 50 МБ до 200 МБ (в зависимости от внутренней реализации широких символов). Это может замедлить работу вашей оболочки, но это не главная проблема.
Основная проблема заключается в том, что приведенная выше команда должна прочитать весь файл в память оболочки и
echo $i
выполнить деление полей в содержимом этого файла$i
. Чтобы разделить поля, весь текст из файла необходимо преобразовать в широкие символы, и именно на это тратится большая часть времени.Я сделал несколько тестов с медленным случаем и получил эти результаты:
Причина, по которой ksh93 является самой быстрой, заключается в том, что ksh93 использует не
mbtowc()
libc, а собственную реализацию.КСТАТИ: Стефан ошибается, что размер чтения имеет некоторое влияние, я скомпилировал оболочку Bourne для чтения 4096 байт вместо 128 байт и получил одинаковую производительность в обоих случаях.
источник
i=`cat input`
не делит поля, это то,echo $i
что делает. Время, потраченное на,i=`cat input`
будет незначительным по сравнению сecho $i
, но не по сравнению сcat input
одним, и в случаеbash
, разница в лучшем случае из-заbash
небольшого чтения. Переключение с 128 на 4096 не повлияет на производительностьecho $i
, но это был не тот вопрос, который я делал.echo $i
будет значительно различаться в зависимости от содержимого ввода и файловой системы (если она содержит символы IFS или символы глобуса), поэтому в своем ответе я не сравнивал оболочки. Например, здесь на выходеyes | ghead -c50M
ksh93 самый медленный из всех, ноyes | ghead -c50M | paste -sd: -
он самый быстрый.В обоих случаях цикл будет выполняться только дважды (один раз для слова
seq
и один раз для слова10
).Кроме того, оба будут объединять соседние пробелы и отбрасывать начальные / конечные пробелы, так что выходные данные не обязательно будут двумя копиями входных данных.
Первый
второй
Одной из причин, почему
echo
это медленнее, может быть то, что ваша переменная без кавычек разделяется в пробеле на отдельные слова. Для 50 МБ это будет много работы. Цитировать переменные!Я предлагаю вам исправить эти ошибки, а затем пересмотреть время.
Я проверил это локально. Я создал файл размером 50 МБ, используя вывод
tar cf - | dd bs=1M count=50
. Я также расширил циклы, чтобы они выполнялись с коэффициентом х100, чтобы время было уменьшено до разумного значения (я добавил еще один цикл вокруг всего кода:for k in $(seq 100); do
...done
). Вот время:Как вы можете видеть, нет никакой разницы, но, если что-то, версия, содержащаяся
echo
, работает немного быстрее. Если я удаляю кавычки и запускаю вашу неработающую версию 2, время удваивается, показывая, что оболочке приходится выполнять гораздо больше работы, чего следует ожидать.источник
cat
очень, очень быстро, чемecho
. Первый скрипт выполняется в среднем за 3 секунды, а второй - за 54 секунды.tar cf - | dd bs=1M count=50
? Делает ли он обычный файл с такими же символами внутри? Если это так, в моем случае входной файл совершенно нерегулярный со всеми видами символов и пробелов. И снова я использовал,time
как вы использовали, и результат был тот, который я сказал: 54 секунды против 3 секунд.read
намного быстрее чемcat
Я думаю, что каждый может проверить это:
cat
занимает 9,372 секунды.echo
занимает.232
секундыread
в 40 раз быстрее .Мой первый тест при
$p
отображении на экранеread
был в 48 раз быстрее, чемcat
.источник
echo
поставить 1 строку на экране. Во втором примере вы делаете то, что вы помещаете содержимое файла в переменную, а затем печатаете эту переменную. В первом вы сразу же выводите контент на экран.cat
оптимизирован для этого использования.echo
не является. Также не рекомендуется помещать 50 Мбайт в переменную окружения.источник
echo
быть оптимизированным для написания текста?Дело не в том, что эхо быстрее, а в том, что вы делаете:
В одном случае вы читаете с ввода и пишете на вывод напрямую. Другими словами, все, что читается с ввода через cat, отправляется на вывод через stdout.
В другом случае вы читаете из ввода в переменную в памяти и затем записываете содержимое переменной в выводе.
Последнее будет намного медленнее, особенно если ввод 50 МБ.
источник