Pourquoi “écho” est-il tellement plus rapide que “tactile”?

116

J'essaie de mettre à jour l'horodatage à l'heure actuelle sur tous les fichiers XML de mon répertoire (de manière récursive). J'utilise Mac OSX 10.8.5.

Sur environ 300 000 fichiers, la echocommande suivante prend 10 secondes :

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

Cependant, la touchcommande suivante prend 10 minutes ! :

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

Pourquoi l'écho est-il tellement plus rapide que le toucher ici?

polym
la source
20
Juste une remarque de côté: Vous ne savez que ces deux commandes ne sont pas équivalentes, ne vous? Au moins pour Unix / Linux, le echo >> $fileva ajouter une nouvelle ligne à $fileet donc le modifier. Je suppose que ce sera la même chose pour OS / X. Si vous ne le souhaitez pas, utilisez echo -n >> $file.
Dubu
2
Aussi ne serait pas touch `find . -name "*.xml"` encore plus rapide que les deux ci-dessus?
elmo
4
Ou considérez juste>>$file
gerrit
8
Pas une réponse à la question explicite, mais pourquoi invoquer touchautant de fois? find . -name '*.xml' -print0 | xargs -0 touchinvoque touchbeaucoup moins de fois (éventuellement une seule fois). Fonctionne sous Linux, devrait fonctionner sous OS X.
Mike Renfro
3
@elmo liste d'arguments trop longue (facilement, avec 300.000 fichiers ...)
Rmano

Réponses:

161

En bash, touchest un fichier binaire externe, mais echoun shell intégré :

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

Etant donné qu’il touchs’agit d’un fichier binaire externe et que vous appelez touchune fois par fichier, le shell doit créer 300 000 instances de touch, ce qui prend beaucoup de temps.

echo, cependant, est un shell intégré, et l’exécution de shell n’a pas besoin de forking. Au lieu de cela, le shell actuel effectue toutes les opérations et aucun processus externe n'est créé; c'est la raison pour laquelle c'est tellement plus rapide.

Voici deux profils des opérations du shell. Vous pouvez voir que l'on passe beaucoup de temps à cloner de nouveaux processus lors de l'utilisation touch. L'utilisation /bin/echodu shell intégré devrait donner un résultat beaucoup plus comparable.


Utiliser le toucher

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

Utilisation de l'écho

$ 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
la source
1
Avez-vous compilé strace sur OS X ou exécuté votre test sur un autre système d'exploitation?
bmike
1
@bmike Mon test est sur Linux, mais le principe est identique.
Chris Down
Je suis tout à fait d’accord - voyez mon commentaire sur la question principale de savoir comment / bin / echo est aussi lent que / bin / touch pour que le raisonnement soit sain. Je voulais juste reproduire le timing de strace et j'ai échoué avec dtruss / dtrace et la syntaxe bash -c ne fonctionne pas comme prévu sur OS X non plus.
bmike
71

Comme d' autres ont répondu, à l' aide echosera plus rapide que touchcomme echoune commande qui est souvent (mais pas besoin d'être) à la coque intégrée. Son utilisation supprime la surcharge du noyau associée à l'exécution d'un nouveau processus pour chaque fichier que vous obtenez touch.

Toutefois, notez que le moyen le plus rapide d’atteindre cet effet est toujours d’utiliser touch, mais plutôt que d’exécuter le programme une fois pour chaque fichier, il est possible d’utiliser l’ -execoption avec findpour s’assurer qu’il n’est exécuté que quelques fois. Cette approche sera généralement plus rapide car elle évite la surcharge associée à une boucle shell:

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

Utiliser +(par opposition à \;) avec find ... -execexécute la commande une seule fois, si possible, avec chaque fichier comme argument. Si la liste d'arguments est très longue (comme c'est le cas avec 300 000 fichiers), plusieurs analyses seront effectuées avec une liste d'arguments dont la longueur est proche de la limite ( ARG_MAXsur la plupart des systèmes).

Un autre avantage de cette approche est qu’elle se comporte de manière robuste avec les noms de fichiers contenant tous les caractères d’espacement, ce qui n’est pas le cas de la boucle d’origine.

Graeme
la source
17
+1pour avoir souligné l' +argument de recherche . Je pense que beaucoup de gens ne sont pas conscients de cela (je ne l'étais pas).
gerrit
7
Toutes les versions de findn'ont pas l' +argument. Vous pouvez obtenir un effet similaire en effectuant un piping to xargs.
Barmar
5
@Barmar, la +pièce est requise par POSIX, elle devrait donc être portable. -print0n'est pas.
Graeme
1
Je rencontre encore parfois des implémentations qui ne l’ont pas. YMMV.
Barmar
1
@ChrisDown, quelque chose que j'ai découvert est que la Busybox finda l'option disponible mais la traite simplement comme un ;sous la surface.
Graeme
29

echoest un shell intégré. D'autre part, touchest un binaire externe.

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

L'intégration de Shell est beaucoup plus rapide car il n'y a pas de temps système impliqué dans le chargement du programme, c'est-à-dire qu'il n'y a pas fork/ execimpliqué. En tant que tel, vous observeriez une différence de temps significative lorsque vous exécutez une commande interne par rapport à une commande externe un grand nombre de fois.

C'est la raison pour laquelle des utilitaires similaires timesont disponibles en tant que commandes intégrées au shell.

Vous pouvez obtenir la liste complète des commandes intégrées au shell en disant:

enable -p

Comme mentionné ci-dessus, l'utilisation de l' utilitaire par opposition à l'utilisation intégrée entraîne une dégradation significative des performances. Voici les statistiques du temps pris pour créer environ 9 000 fichiers à l'aide de la commande intégrée echo et de l' utilitaire 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
la source
Et je pense qu’il existe un echofichier binaire sur la plupart des systèmes (pour moi, c’est le cas /bin/echo), vous pouvez donc réessayer les tests de minutage en l’utilisant à la place de la commande intégrée
Michael Mrozek
@MichaelMrozek Ajout de tests de synchronisation pour les fonctions intégrée et binaire.
devnull