Dlaczego „echo” jest o wiele szybsze niż „dotyk”?


116

Próbuję zaktualizować znacznik czasu do bieżącego czasu we wszystkich plikach xml w moim katalogu (rekurencyjnie). Używam Mac OSX 10.8.5.

W przypadku około 300 000 plików następujące echopolecenie zajmuje 10 sekund :

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

Jednak następujące touchpolecenie zajmuje 10 minut ! :

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

Dlaczego echo jest tutaj znacznie szybsze niż dotyk?


20
Tylko uwaga strona: Ty nie wiesz, że te dwa polecenia nie są równoważne, prawda? Przynajmniej dla Uniksa / Linuksa echo >> $filedoda nowy wiersz $filei tym samym go zmodyfikuje. Zakładam, że będzie tak samo dla OS / X. Jeśli tego nie chcesz, użyj echo -n >> $file.
Dubu,

2
Czy nie touch `find . -name "*.xml"` byłoby nawet szybsze niż oba powyższe?
Elmo

4
Lub >>$file
weź

8
Nie jest to odpowiedź na jednoznaczne pytanie, ale po touchco w ogóle wywoływać tyle razy? find . -name '*.xml' -print0 | xargs -0 touchwywołuje touchznacznie mniej razy (być może tylko raz). Działa na systemie Linux, powinien działać na systemie OS X.
Mike Renfro

3
Zbyt długa lista argumentów @elmo (łatwo, z 300 000 plików ...)
Rmano,

Odpowiedzi:


161

W bash touchjest zewnętrznym plikiem binarnym, ale echojest wbudowaną powłoką :

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

Ponieważ touchjest to zewnętrzny plik binarny, a ty wywołujesz touchraz na plik, powłoka musi utworzyć 300 000 instancji touch, co zajmuje dużo czasu.

echojest jednak wbudowany w powłokę, a wykonanie wbudowanych powłok nie wymaga w ogóle rozwidlenia. Zamiast tego bieżąca powłoka wykonuje wszystkie operacje i nie są tworzone żadne procesy zewnętrzne; to jest powód, dla którego jest o wiele szybszy.

Oto dwa profile operacji powłoki. Widać, że dużo czasu spędza się na klonowaniu nowych procesów podczas korzystania touch. Użycie /bin/echozamiast wbudowanej powłoki powinno dać znacznie bardziej porównywalny wynik.


Korzystanie z dotyku

$ 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
[...]

Za pomocą echa

$ 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
Czy skompilowałeś strace na OS X lub uruchomiłeś test na innym OS?
bmike

1
@bike Mój test jest na Linuksie, ale zasada jest identyczna.
Chris Down

Całkowicie się zgadzam - patrz mój komentarz do głównego pytania o to, jak / bin / echo jest tak wolne jak / bin / touch, więc rozumowanie jest prawidłowe. Chciałem po prostu odtworzyć timing strace i nie udało mi się użyć dtruss / dtrace, a składnia bash -c również nie działa zgodnie z oczekiwaniami w OS X.
bmike

71

Jak odpowiedzieli inni, użycie echobędzie szybsze niż touchw echoprzypadku polecenia, które jest powszechnie (choć nie musi) być wbudowane w powłokę. Używanie go zwalnia z narzutu jądra związanego z uruchamianiem rozpoczynania nowego procesu dla każdego otrzymanego pliku touch.

Należy jednak pamiętać, że najszybszym sposobem na osiągnięcie tego efektu jest nadal użycie touch, ale zamiast uruchamiania programu raz dla każdego pliku, można użyć -execopcji z, findaby upewnić się, że jest uruchamiany tylko kilka razy. Takie podejście jest zwykle szybsze, ponieważ pozwala uniknąć narzutu związanego z pętlą powłoki:

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

Użycie +(w przeciwieństwie do \;) z find ... -execuruchamia polecenie tylko raz, jeśli to możliwe, z każdym plikiem jako argumentem. Jeśli lista argumentów jest bardzo długa (jak ma to miejsce w przypadku 300 000 plików), wykonanych zostanie wiele uruchomień z listą argumentów o długości zbliżonej do limitu ( ARG_MAXw większości systemów).

Kolejną zaletą tego podejścia jest to, że działa on solidnie z nazwami plików zawierającymi wszystkie znaki spacji, co nie ma miejsca w przypadku oryginalnej pętli.


17
+1za wskazanie +argumentu find . Myślę, że wiele osób nie jest tego świadomych (nie byłem).
gerrit

7
Nie wszystkie wersje findmają +argument. Możesz uzyskać podobny efekt, przesyłając do xargs.
Barmar

5
@Barmar, +część jest wymagana przez POSIX, więc powinna być przenośna. -print0nie jest.
Graeme,

1
Nadal czasami spotykam się z implementacjami, które go nie mają. YMMV.
Barmar

1
@ChrisDown, odkryłem, że Busybox findma dostępną opcję, ale traktuje ją jak ;pod powierzchnią.
Graeme,

29

echojest wbudowaną powłoką. Z drugiej strony touchjest zewnętrznym plikiem binarnym.

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

Wbudowane powłoki są znacznie szybsze, ponieważ ładowanie programu nie wiąże się z żadnymi kosztami, tzn. Nie ma fork/ jest execzaangażowane. W związku z tym można zaobserwować znaczną różnicę czasu podczas wykonywania polecenia wbudowanego w porównaniu z poleceniem zewnętrznym wiele razy.

Z tego powodu narzędzia takie timesą dostępne jako wbudowane powłoki.

Możesz uzyskać pełną listę wbudowanych powłok, mówiąc:

enable -p

Jak wspomniano powyżej, użycie narzędzia w przeciwieństwie do wbudowanego powoduje znaczny spadek wydajności. Poniżej znajdują się statystyki czasu tworzenia ~ 9000 plików przy użyciu wbudowanego echo i narzędzia 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

I myślę, że echona większości systemów jest plik binarny (dla mnie to jest /bin/echo), więc możesz spróbować ponownie testów czasowych, używając go zamiast wbudowanego
Michael Mrozek

@MichaelMrozek Dodano testy czasowe dla wbudowanego i binarnego pliku.
devnull
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.