Почему «эхо» намного быстрее, чем «прикосновение»?

116

Я пытаюсь обновить метку времени до текущего времени для всех файлов XML в моем каталоге (рекурсивно). Я использую Mac OSX 10.8.5.

Приблизительно для 300 000 файлов следующая echoкоманда занимает 10 секунд :

for file in `find . -name "*.xml"`; do echo >> $file; done

Однако следующая touchкоманда занимает 10 минут ! :

for file in `find . -name "*.xml"`; do touch $file; done

Почему эхо намного быстрее, чем прикосновение?

Polym
источник
20
Только сторона замечание: Вы же знаете , что эти две команды не эквивалентны, не так ли? По крайней мере для Unix / Linux, echo >> $fileон добавит новую строку $fileи, таким образом , изменит ее. Я предполагаю, что это будет то же самое для OS / X. Если вы не хотите этого, используйте echo -n >> $file.
Дабу
2
Также не touch `find . -name "*.xml"` будет даже быстрее, чем оба из вышеперечисленного?
Элмо
4
Или рассмотрим просто>>$file
Gerrit
8
Не ответ на явный вопрос, но зачем touchвообще так много раз вызывать ? find . -name '*.xml' -print0 | xargs -0 touchвызывает touchнамного меньше раз (возможно, только один раз). Работает на Linux, должен работать на OS X.
Майк Ренфро
3
Список аргументов @elmo слишком длинный (легко, с 300.000 файлами ...)
Rmano

Ответы:

161

В bash touchэто внешний бинарный файл, но echoэто встроенная оболочка :

$ type echo
echo is a shell builtin
$ type touch
touch is /usr/bin/touch

Поскольку touchэто внешний двоичный файл, и вы вызываете его touchодин раз для файла, оболочка должна создать 300 000 экземпляров touch, что занимает много времени.

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

Вот два профиля операций оболочки. Вы можете видеть, что при использовании вы тратите много времени на клонирование новых процессов touch. Использование /bin/echoвместо встроенной оболочки должно показывать гораздо более сопоставимый результат.


Используя сенсорный

$ strace -c -- bash -c 'for file in a{1..10000}; do touch "$file"; done'
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 56.20    0.030925           2     20000     10000 wait4
 38.12    0.020972           2     10000           clone
  4.67    0.002569           0     80006           rt_sigprocmask
  0.71    0.000388           0     20008           rt_sigaction
  0.27    0.000150           0     10000           rt_sigreturn
[...]

Используя эхо

$ strace -c -- bash -c 'for file in b{1..10000}; do echo >> "$file"; done'
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 34.32    0.000685           0     50000           fcntl
 22.14    0.000442           0     10000           write
 19.59    0.000391           0     10011           open
 14.58    0.000291           0     20000           dup2
  8.37    0.000167           0     20013           close
[...]
Крис Даун
источник
1
Вы компилировали strace на OS X или запускали тест на другой ОС?
bmike
1
@bmike Мой тест на Linux, но принцип идентичен.
Крис Даун
Я полностью согласен - см. Мой комментарий к основному вопросу о том, как / bin / echo работает так же медленно, как / bin / touch, поэтому рассуждения обоснованы. Я просто хотел воспроизвести синхронизацию strace и потерпел неудачу при использовании dtruss / dtrace, а синтаксис bash -c также не работает должным образом в OS X.
bmike
71

Как и другие ответили, используя echoбудет быстрее , чем , touchкак echoэто команда , которая обычно (хотя и не обязательно быть) , встроенные в оболочку. Его использование избавляет от накладных расходов ядра, связанных с запуском запуска нового процесса для каждого файла, который вы получаете touch.

Однако обратите внимание, что самым быстрым способом достижения этого эффекта по-прежнему является использование touch, но вместо того, чтобы запускать программу один раз для каждого файла, можно использовать -execопцию с, findчтобы обеспечить запуск только несколько раз. Этот подход обычно будет быстрее, поскольку он позволяет избежать накладных расходов, связанных с циклом оболочки:

find . -name "*.xml" -exec touch {} +

Использование +(в отличие от \;) с find ... -execзапускает команду только один раз, если это возможно, с каждым файлом в качестве аргумента. Если список аргументов очень длинный (как в случае с 300 000 файлов), будет выполнено несколько запусков со списком аргументов, длина которого близка к пределу ( ARG_MAXв большинстве систем).

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

Graeme
источник
17
+1для указания +аргумента поиска . Я думаю, что многие люди не знают об этом (я не был).
Gerrit
7
Не все версии findимеют +аргумент. Вы можете получить аналогичный эффект по трубопроводу xargs.
Бармар
5
@ Barmar, +часть требуется POSIX, поэтому должна быть переносимой. -print0нет.
Грэм
1
Я все еще иногда сталкиваюсь с реализациями, у которых этого нет. YMMV.
Бармар
1
@ChrisDown, кое-что, что я обнаружил, - то, что у Busybox findесть доступная опция, но только рассматривает это как ;под поверхностью.
Грэм
29

echoэто встроенная оболочка С другой стороны, touchэто внешний двоичный файл.

$ type echo
echo is a shell builtin
$ type touch
touch is hashed (/usr/bin/touch)

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

Это причина того, что подобные утилиты timeдоступны в виде встроенных командных оболочек.

Вы можете получить полный список встроенных команд оболочки, сказав:

enable -p

Как уже упоминалось выше, использование утилиты, а не встроенной, приводит к значительному снижению производительности. Ниже приведена статистика времени, затраченного на создание ~ 9000 файлов с использованием встроенной программы echo и утилиты echo :

# Using builtin
$ time bash -c 'for i in {1000..9999}; do echo > $i; done'

real    0m0.283s
user    0m0.100s
sys 0m0.184s

# Using utility /bin/echo
$ time bash -c 'for i in {1000..9999}; do /bin/echo > $i; done'

real    0m8.683s
user    0m0.360s
sys 0m1.428s
devnull
источник
И я думаю, что в echoбольшинстве систем есть бинарный файл (для меня это /bin/echo), поэтому вы можете повторить тесты синхронизации, используя его вместо встроенного
Майкл Мрозек
@MichaelMrozek Добавлены временные тесты для встроенного и двоичного файлов.
devnull