Warum ist "Echo" so viel schneller als "Touch"?

116

Ich versuche, den Zeitstempel für alle XML-Dateien in meinem Verzeichnis (rekursiv) auf die aktuelle Zeit zu aktualisieren. Ich verwende Mac OSX 10.8.5.

Für ungefähr 300.000 Dateien echodauert der folgende Befehl 10 Sekunden :

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

Der folgende touchBefehl dauert jedoch 10 Minuten ! :

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

Warum ist das Echo hier so viel schneller als die Berührung?

Polym
quelle
20
Nur eine Randbemerkung: Sie wissen , dass diese beiden Befehle nicht gleichbedeutend sind, oder? Zumindest für Unix / Linux echo >> $filewird eine neue Zeile angehängt $fileund somit geändert . Ich gehe davon aus, dass es für OS / X dasselbe sein wird. Wenn Sie das nicht wollen, verwenden Sie echo -n >> $file.
Dubu
2
Wäre auch nicht touch `find . -name "*.xml"` noch schneller als die beiden oben?
Elmo
4
Oder >>$file
denken Sie
8
Keine Antwort auf die explizite Frage, aber warum überhaupt touchso oft aufrufen ? find . -name '*.xml' -print0 | xargs -0 touchruft touchviel seltener auf (möglicherweise nur einmal). Funktioniert unter Linux, sollte unter OS X funktionieren.
Mike Renfro
3
@ Almo Argument Liste zu lang (leicht, mit 300.000 Dateien ...)
Rmano

Antworten:

161

In bash touchist es eine externe Binärdatei, aber es echoist eine eingebaute Shell :

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

Da touches sich um eine externe Binärdatei handelt und Sie diese toucheinmal pro Datei aufrufen , muss die Shell 300.000 Instanzen von erstellen touch, was sehr lange dauert.

echoEs handelt sich jedoch um ein Shell-Builtin, und für die Ausführung von Shell-Builtins ist kein Forking erforderlich. Stattdessen führt die aktuelle Shell alle Vorgänge aus, und es werden keine externen Prozesse erstellt. das ist der Grund, warum es so viel schneller ist.

Hier sind zwei Profile der Shell-Operationen. Sie sehen, dass beim Klonen neuer Prozesse viel Zeit aufgewendet wird touch. Die Verwendung /bin/echoder eingebauten Shell sollte zu einem viel vergleichbareren Ergebnis führen.


Mit Berührung

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

Echo verwenden

$ 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
[...]
Chris Down
quelle
1
Hast du strace unter OS X kompiliert oder deinen Test auf einem anderen Betriebssystem ausgeführt?
bmike
1
@bmike Mein Test ist unter Linux, aber das Prinzip ist identisch.
Chris Down
Ich stimme vollkommen zu - siehe meinen Kommentar zur Hauptfrage, wie / bin / echo so langsam ist wie / bin / touch, damit die Argumentation stichhaltig ist. Ich wollte nur das Timing von strace reproduzieren und bin mit dtruss / dtrace gescheitert, und die bash -c-Syntax funktioniert auch unter OS X nicht wie erwartet.
bmike
71

Wie andere geantwortet haben, ist using echoschneller als touchas echoein Befehl, der normalerweise in die Shell integriert ist (obwohl dies nicht erforderlich ist). Durch die Verwendung entfällt der Kernel-Overhead, der mit dem Starten eines neuen Prozesses für jede Datei verbunden ist, die Sie erhalten touch.

Beachten Sie jedoch, dass der schnellste Weg, um diesen Effekt zu erzielen, immer noch die Verwendung touchist. Statt das Programm einmal für jede Datei auszuführen , können Sie die -execOption mit verwenden, findum sicherzustellen, dass sie nur einige Male ausgeführt wird. Dieser Ansatz ist normalerweise schneller, da der mit einer Shell-Schleife verbundene Overhead vermieden wird:

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

Bei Verwendung von +(im Gegensatz zu \;) mit wird find ... -execder Befehl, wenn möglich, nur einmal mit jeder Datei als Argument ausgeführt. Wenn die Argumentliste sehr lang ist (wie dies bei 300.000 Dateien der Fall ist), werden mehrere Durchläufe mit einer Argumentliste durchgeführt, deren Länge nahe am Limit liegt ( ARG_MAXauf den meisten Systemen).

Ein weiterer Vorteil dieses Ansatzes besteht darin, dass er sich stabil mit Dateinamen verhält, die alle Leerzeichen enthalten, was bei der ursprünglichen Schleife nicht der Fall ist.

Graeme
quelle
17
+1um auf das find- +Argument hinzuweisen . Ich denke, viele Leute sind sich dessen nicht bewusst (ich war es nicht).
Gerrit
7
Nicht alle Versionen von findhaben das +Argument. Sie können einen ähnlichen Effekt erzielen, indem Sie an leiten xargs.
Barmar
5
@Barmar, das +Teil wird von POSIX benötigt, sollte also portabel sein. -print0ist nicht.
Graeme
1
Ich stoße noch gelegentlich auf Implementierungen, die es nicht haben. YMMV.
Barmar
1
@ ChrisDown, etwas, das ich entdeckt habe, ist, dass die Busybox finddie verfügbare Option hat, sie aber einfach wie eine ;unter der Oberfläche behandelt.
Graeme
29

echoist eine eingebaute Shell. Auf der anderen Seite touchist eine externe Binärdatei.

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

Shell-Buildins sind viel schneller, da beim Laden des Programms kein Overhead anfällt , dh es ist kein fork/ execbeteiligt. Als solches würden Sie einen signifikanten Zeitunterschied feststellen, wenn Sie einen eingebauten Befehl häufig im Vergleich zu einem externen Befehl ausführen.

Dies ist der Grund, warum Dienstprogramme wie diese timeals Shell-Buildins verfügbar sind.

Sie können die vollständige Liste der Shell-Buildins abrufen, indem Sie sagen:

enable -p

Wie oben erwähnt, führt die Verwendung des Dienstprogramms im Gegensatz zum eingebauten Dienstprogramm zu einer signifikanten Leistungsverschlechterung. Im Folgenden finden Sie eine Statistik über die Zeit, die zum Erstellen von ~ 9000 Dateien mithilfe der integrierten Funktion echo und des Dienstprogramms benötigt wurde 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
quelle
Und ich denke, es gibt eine echoBinärdatei auf den meisten Systemen (für mich ist es /bin/echo), so dass Sie die Timing-Tests mit dieser anstelle der eingebauten wiederholen können
Michael Mrozek
@MichaelMrozek Timing-Tests für das Builtin und das Binary hinzugefügt.
Devnull