Чому "відлуння" настільки швидше, ніж "дотик"?


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

Чому відлуння настільки швидше, ніж торкання тут?


20
Тільки сторона зауваження: Ви ж знаєте , що ці дві команди не еквівалентні, чи не так? Принаймні для Unix / Linux, echo >> $fileзасіб додасть новий рядок $fileі таким чином змінить його. Я припускаю, що це буде те саме для OS / X. Якщо ви цього не хочете, використовуйте echo -n >> $file.
Дубу

2
Також не touch `find . -name "*.xml"` було б навіть швидше, ніж обидва вищезгадані?
elmo

4
Або подумайте просто>>$file
Герріт

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
Ви складали стрази на OS X або запускали тест на іншій ОС?
bmike

1
@bmike Мій тест є на Linux, але принцип ідентичний.
Кріс Даун

Я повністю згоден - дивіться мій коментар до головного питання про те, як / bin / echo є настільки ж повільним, як / bin / touch, тому міркування є надійними. Я просто хотів відтворити терміни страйсу і не вдалося використовувати 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у більшості систем).

Ще одна перевага цього підходу полягає в тому, що він поводиться надійно з іменами файлів, що містять усі символи пробілу, що не відбувається з оригінальним циклом.


17
+1для вказівки +аргументу знаходження . Я думаю, що багато людей цього не знають (я не був).
Герріт

7
Не всі версії програми findмають +аргумент. Ви можете отримати подібний ефект, проклавши в xargs.
Бармар

5
@Barmar, +частина вимагається POSIX, тому має бути портативною. -print0ні.
Graeme

1
Я все ще час від часу стикаюся з реалізаціями, у яких її немає. YMMV.
Бармар

1
@ChrisDown, Щось я виявив, це те, що у Busybox findє доступний варіант, але він просто розглядає його як ;під поверхнею.
Graeme

29

echoє вбудованою оболонкою. З іншого боку, touchце зовнішній бінарний.

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

Shell вбудовані набагато швидше, оскільки немає завантаження накладних програм, тобто немає 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

І я думаю, що в echoбільшості систем є двійкове (для мене це /bin/echo), тож ви можете повторити тести часу, використовуючи це замість вбудованого
Michael Mrozek

@MichaelMrozek Додано терміни тестування на вбудований та двійковий.
devnull
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.