web-dev-qa-db-fra.com

Comment `Yes` écrit-il dans le fichier si rapidement?

Laissez-moi vous donner un exemple:

$ timeout 1 yes "GNU" > file1
$ wc -l file1
11504640 file1
$ for ((sec0=`date +%S`;sec<=$(($sec0+5));sec=`date +%S`)); do echo "GNU" >> file2; done
$ wc -l file2
1953 file2

Ici, vous pouvez voir que la commande yes écrit 11504640 lignes dans une seconde pendant que je peux écrire seulement 1953 lignes en 5 secondes à l'aide de Bash for et echo.

Comme suggéré dans les commentaires, il existe différentes astuces pour le rendre plus efficace, mais rien ne se rapproche de la vitesse de yes:

$ ( while :; do echo "GNU" >> file3; done) & pid=$! ; sleep 1 ; kill $pid
[1] 3054
$ wc -l file3
19596 file3
$ timeout 1 bash -c 'while true; do echo "GNU" >> file4; done'
$ wc -l file4
18912 file4

Ceux-ci peuvent écrire jusqu'à 20 mille lignes dans une seconde. Et ils peuvent être encore améliorés pour:

$ timeout 1 bash -c 'while true; do echo "GNU"; done >> file5' 
$ wc -l file5
34517 file5
$ ( while :; do echo "GNU"; done >> file6 ) & pid=$! ; sleep 1 ; kill $pid
[1] 5690
$ wc -l file6
40961 file6

Ceux-ci nous amènent jusqu'à 40 mille lignes dans une seconde. Mieux, mais toujours un cri loin de yes qui peut écrire environ 11 millions de lignes dans une seconde!

Donc, Comment yes écrire dans le fichier si rapidement?

58
Pandya

Une meilleure question serait la raison pour laquelle votre shell écria-t-il le fichier si lentement. Tout programme compilé autonome qui utilise la rédaction de fichiers syscalls de manière responsable (ne pas rincer tous les caractères à la fois) le ferait raisonnablement rapide. Ce que vous faites, c'est écrire des lignes dans un interprété Langue (la coquille) et, en outre, vous faites un lot de inutile opérations de sortie d'entrée. Qu'est-ce que yes fait:

  • ouvre un fichier pour écrire
  • appels optimisés et compilés des fonctions d'écriture à un flux
  • le flux est tamponné, de sorte qu'un syscall (un interrupteur coûteux en mode noyau) arrive très rarement, dans de grandes morceaux
  • ferme un fichier

Quel est votre script:

  • se lit dans une ligne de code
  • interprète le code, faisant beaucoup d'opérations supplémentaires pour analyser votre contribution et comprendre quoi faire
  • pour chaque itération de la boucle tandis que la boucle (ce qui n'est probablement pas bon marché dans une langue interprétée):
    • appelez la commande externe date et stockez sa sortie (uniquement dans la version originale - dans la version révisée, vous gagnez un facteur 10 en ne le faisant pas)
    • testez si la condition de terminaison de la boucle est remplie
    • Open Un fichier en mode annexe
    • payse echo commande, reconnaissez-la (avec un code de correspondance de modèle) en tant que coque intégrée, d'une expansion des paramètres d'appel et de tout le reste de l'argument "GNU", et écrivez enfin la ligne au fichier ouvert.
    • fermeture le fichier à nouveau
    • répétez le processus

Les parties coûteuses: toute l'interprétation est extrêmement chère (BASH effectue une énormément de prétraitement de toutes les entrées - votre chaîne pourrait potentiellement contenir une substitution variable, une substitution de processus, une expansion de la corset, des caractères d'échappement, etc.), chaque appel d'une invention est Probablement une instruction de commutation avec redirection vers une fonction qui traite de la construction, et de manière très important, vous ouvrez et fermez un fichier pour chaque ligne de sortie. Vous pourriez mettre >> file En dehors de la boucle tandis que la boucle pour le faire beaucoup plus rapide, mais vous êtes toujours dans une langue interprétée. Vous avez beaucoup de chance que echo est une coque intégrée, pas une commande externe - sinon, votre boucle impliquerait la création d'un nouveau processus (Fork & Exec) sur chaque itération. Ce qui empêcherait le processus à une halte - vous avez vu à quel point une commande est coûteuse lorsque vous aviez la commande date dans la boucle.

20
orion

Les autres réponses ont abordé les points principaux. Sur une note latérale, vous pouvez augmenter le débit de votre boucle de votre temps en écrivant au fichier de sortie à la fin du calcul. Comparer:

$ i=0;time while  [ $i -le 1000 ]; do ((++i)); echo "GNU" >>/tmp/f; done;

real    0m0.080s
user    0m0.032s
sys     0m0.037s

avec

$ i=0;time while  [ $i -le 1000 ]; do ((++i)); echo "GNU"; done>>/tmp/f;

real    0m0.030s
user    0m0.019s
sys     0m0.011s
11
Apoorv Gupta