Почему существует такая разница во времени выполнения эха и кота?

Отвечая на этот вопрос, я задал другой вопрос:
Я думал, что следующие сценарии делают одно и то же, а второе – намного быстрее, потому что первый использует 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 было масштабным процессом. У меня также возникли проблемы со вторым скриптом, например размер выходного файла был ниже ожидаемого.

Я также проверил man-страницу echo и cat чтобы сравнить их:

echo – отображение строки текста

cat – конкатенация файлов и печать на стандартном выходе

Но я не понял.

Так:

  • Почему кошка настолько быстрая, и эхо во втором скрипте настолько медленное?
  • Или проблема с переменной i ? (потому что на странице man 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 .

  • Запустите команду, сохраненную в переменной, чтобы эхо в файл
  • Возврат каретки с командой эха
  • Почему «эхо» намного быстрее, чем «прикосновение»?
  • Не удается отразить цветной текст при вставке в строку состояния в dwm
  • Echo не печатает правильное значение для переменной
  • Команда Whatis (встроенная оболочка для исполняемых программ)
  • Что делает #var?
  • Почему работает «echo os.system (« / bin / bash »)?
  • 6 Solutions collect form web for “Почему существует такая разница во времени выполнения эха и кота?”

    Здесь есть несколько вещей.

     i=`cat input` 

    может быть дорогостоящим, и между раковинами существует множество вариаций.

    Это функция, называемая подстановкой команд. Идея состоит в том, чтобы сохранить весь вывод команды за вычетом символов новой строки в переменной i в памяти.

    Для этого shells выдает команду в подоболочку и считывает ее вывод через трубку или сокет. Здесь вы видите много вариаций. В файле 50MiB я вижу, например, что bash в 6 раз меньше, чем ksh93, но немного быстрее, чем zsh, и в два раза быстрее, чем yash.

    Основная причина медленной работы bash заключается в том, что она считывает из канала 128 байт за раз (в то время как другие оболочки читают 4KiB или 8KiB за раз) и наказываются служебными данными системного вызова.

    zsh необходимо выполнить некоторую пост-обработку, чтобы избежать NUL-байтов (другие оболочки разбиваются на NUL-байты), а yash делает еще более тяжелую обработку, анализируя многобайтовые символы.

    Все оболочки должны отделять конечные символы новой строки, которые они могут делать более или менее эффективно.

    Некоторые из них могут обрабатывать NUL байты более грациозно, чем другие, и проверять их присутствие.

    Затем, когда у вас есть эта большая переменная в памяти, любые манипуляции с ней обычно связаны с распределением большего количества памяти и копированием данных.

    Здесь вы проходите (намерены передать) содержимое переменной для echo .

    К счастью, echo встроено в вашу оболочку, иначе выполнение скорее всего потерпит неудачу с слишком длинной ошибкой списка . Даже тогда создание массива списка аргументов, возможно, потребует копирования содержимого переменной.

    Другая основная проблема в подменю подстановки команд заключается в том, что вы вызываете оператор split + glob (забывая процитировать переменную).

    Для этого оболочкам нужно обрабатывать строку как строку символов (хотя некоторые оболочки не имеют и в этом отношении не работают), поэтому в локалях UTF-8 это означает синтаксический анализ последовательностей UTF-8 (если это еще не сделано, как yash ), найдите символы $IFS в строке. Если $IFS содержит пробел, табуляцию или новую строку (по умолчанию это так), алгоритм еще более сложный и дорогостоящий. Затем следует выделить и скопировать слова, полученные в результате этого расщепления.

    Часть шара будет еще дороже. Если какое-либо из этих слов содержит glob-символы ( * , ? , [ ), То оболочке придется читать содержимое некоторых каталогов и выполнять дорогостоящее сопоставление шаблонов (например, реализация bash как известно, очень плохо).

    Если вход содержит что-то вроде /*/*/*/../../../*/*/*/../../../*/*/* , это будет очень дорого означает перечисление тысяч каталогов и возможность расширения до нескольких сотен миллионов.

    Тогда echo обычно выполняет некоторую дополнительную обработку. Некоторые реализации расширяют \x последовательностей в полученном аргументе, что означает анализ содержимого и, возможно, другое распределение и копирование данных.

    С другой стороны, ОК, в большинстве оболочек cat не встроена, поэтому это означает, что он обрабатывает процесс и выполняет его (так загружая код и библиотеки), но после первого вызова этот код и содержимое ввода файл будет кэшироваться в памяти. С другой стороны, посредника не будет. cat будет читать большие объемы за раз и писать сразу, без обработки, и ему не нужно выделять огромный объем памяти, просто один буфер, который он повторяет.

    Это также означает, что он намного надежнее, так как он не забивает байты NUL и не обрезает завершающие символы новой строки (и не делает split + glob, хотя вы можете избежать этого, указав эту переменную, и не расширяйте escape-последовательность, хотя вы можете избежать этого, используя printf вместо echo ).

    Если вы хотите оптимизировать его дальше, вместо того, чтобы вызывать cat несколько раз, просто передайте input несколько раз cat .

     yes input | head -n 100 | xargs cat 

    Запустит 3 команды вместо 100.

    Чтобы сделать версию переменной более надежной, вам нужно будет использовать zsh (другие оболочки не могут справиться с NUL байтами) и сделать это:

     zmodload zsh/mapfile var=$mapfile[input] repeat 10 print -rn -- "$var" проект zmodload zsh/mapfile var=$mapfile[input] repeat 10 print -rn -- "$var" 

    Если вы знаете, что вход не содержит NUL байт, то вы можете надежно сделать это POSIXly (хотя он может не работать там, где printf не встроен):

     i=$(cat input && echo .) || exit # add an extra .\n to avoid trimming newlines i=${i%.} # remove that trailing dot (the \n was removed by cmdsubst) n=10 while [ "$n" -gt 10 ]; do printf %s "$i" n=$((n - 1)) done 

    Но это никогда не будет более эффективным, чем использование cat в цикле (если вход очень мал).

    Проблема не в cat и echo , а в отношении забытой переменной кавычки $i .

    В Bourne-подобном сценарии оболочки (кроме zsh ), оставляя переменные unquote, вызывают операции glob+split по переменным.

     $var 

    на самом деле:

     glob(split($var)) 

    Таким образом, с каждой итерацией цикла все содержимое input (исключая завершающие символы новой строки) будет расширяться, разбиваться, подбираться. Весь процесс требует оболочки для выделения памяти, разбора строки снова и снова. Вот почему вы получили плохую производительность.

    Вы можете процитировать эту переменную, чтобы предотвратить glob+split но это не поможет вам, так как когда оболочке все еще нужно построить большой строковый аргумент и сканировать его содержимое для echo (замена встроенного echo на внешнее /bin/echo даст вам список аргументов слишком длинный или не зависит от размера $i ). Большая часть реализации echo не совместима с POSIX, она будет расширять обратную косую черту \x в полученных аргументах.

    С cat оболочке требуется только генерировать процесс для каждой итерации цикла, а cat будет делать копии ввода-вывода. Система также может кэшировать содержимое файла, чтобы сделать процесс cat быстрее.

    Если вы позвоните

     i=`cat input` 

    это позволяет вашему процессу оболочки расти на 50 МБ до 200 МБ (в зависимости от реализации внутреннего широкого символа). Это может сделать вашу оболочку медленной, но это не главная проблема.

    Основная проблема заключается в том, что вышеприведенная команда должна прочитать весь файл в памяти оболочки, а echo $i нужно разбить поле на содержимое этого файла в $i . Чтобы разбить поле, весь текст из файла должен быть преобразован в широкие символы, и именно там большая часть времени проводится.

    Я провел несколько тестов с медленным случаем и получил следующие результаты:

    • Самый быстрый – ksh93
    • Далее моя оболочка Bourne (в 2 раза медленнее, чем ksh93)
    • Далее находится bash (в 3 раза медленнее, чем ksh93)
    • Последнее – ksh88 (7x медленнее ksh93)

    Причина, по которой ksh93 является наиболее быстрой, кажется, что ksh93 не использует mbtowc() из libc, а скорее собственную реализацию.

    BTW: Стефан ошибается, что размер чтения имеет некоторое влияние, я скомпилировал Bourne Shell для чтения в 4096 байтовых блоках вместо 128 байтов и получил ту же производительность в обоих случаях.

    В обоих случаях цикл будет выполняться дважды (один раз для слова 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 

    Одна из причин, почему echo медленнее, может заключаться в том, что ваша некотируемая переменная разделяется на пробелы на отдельные слова. За 50 МБ будет много работы. Ответить переменные!

    Я предлагаю вам исправить эти ошибки, а затем переоценить ваши тайминги.


    Я тестировал это локально. Я создал файл 50 Мбайт, используя вывод tar cf - | dd bs=1M count=50 tar cf - | dd bs=1M count=50 . Я также расширил цикл, чтобы работать на коэффициент х100, так что тайминги были масштабированы до разумного значения (я добавил дополнительный цикл вокруг всего вашего кода: for k in $(seq 100); dodone ). Вот тайминги:

     time ./1.sh real 0m5.948s user 0m0.012s sys 0m0.064s time ./2.sh real 0m5.639s user 0m4.060s sys 0m0.224s 

    Как вы можете видеть, нет никакой реальной разницы, но, если что-либо, версия, содержащая echo , работает немного быстрее. Если я удаляю кавычки и запускаю свою сломанную версию 2, время удваивается, показывая, что оболочке приходится делать гораздо больше работы, что следовало ожидать.

     time ./2original.sh real 0m12.498s user 0m8.645s sys 0m2.732s 

    echo предназначено для размещения 1 строки на экране. Что вы делаете во втором примере, так это то, что вы помещаете содержимое файла в переменную, а затем печатаете эту переменную. В первом вы сразу же помещаете содержимое на экран.

    cat оптимизирован для этого использования. echo – нет. Кроме того, 50Mb в переменной среды не является хорошей идеей.

    Речь идет не об эхо быстрее, а о том, что вы делаете:

    В одном случае вы читаете от ввода и записи до вывода напрямую. Другими словами, все, что считывается с ввода через cat, переходит на вывод через stdout.

     input -> output 

    В другом случае вы читаете из ввода в переменную в памяти, а затем записываете содержимое переменной на выходе.

     input -> variable variable -> output 

    Последний будет намного медленнее, особенно если входной сигнал составляет 50 МБ.

    Interesting Posts

    Как накапливать данные за предыдущие x дней

    Как запустить службу в Netgear R7000 (запасная прошивка)

    Сможет ли ковер летнее время изменить +1 час?

    TrueCrypt ограничивает объем mdadm

    Установить теорию с помощью инструментов unix: «расслабленное» пересечение двух списков, где элемент в наборе 1 является по меньшей мере подстрокой элементов в наборе 2

    настройка клиента ldap только для чтения

    Преобразование текстовых файлов с ASCII в Unicode без какой-либо команды

    Аудит изменений в текущей конфигурации iptables

    Как проверить, установлена ​​ли моя графическая карта на Linux?

    Как перенести виртуальную виртуальную машину VirtualBox на металл

    Как установить C ++ 11 в качестве моего компилятора по умолчанию?

    Какая прошивка работает с D-Link DIR-600?

    Поиск и устранение неисправностей HAProxy на RHEL7

    Как я могу запустить скрипт, когда интернет-соединение вот-вот потеряется?

    Избегайте «Совместное подключение к <host> закрытым» сообщениям

    Linux и Unix - лучшая ОС в мире.