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 des 11504640
lignes en une seconde alors que je ne peux écrire que des 1953
lignes en 5 secondes en utilisant bash for
et echo
.
Comme suggéré dans les commentaires, il existe différentes astuces pour le rendre plus efficace, mais aucune ne correspond à 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 000 lignes en 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
Cela nous amène à 40 000 lignes en une seconde. Mieux, mais loin de là, yes
on peut écrire environ 11 millions de lignes en une seconde!
Alors, comment yes
écrire dans un fichier si rapidement?
date
poids est assez lourd. En outre, le shell doit rouvrir le flux de sortieecho
pour chaque itération de la boucle. Dans le premier exemple, il n'y a qu'une seule invocation de commande avec une seule redirection de sortie, et la commande est extrêmement légère. Les deux ne sont nullement comparables.date
peut être lourd, voir modifier à ma question.timeout 1 $(while true; do echo "GNU">>file2; done;)
est la mauvaise façon d'utilisertimeout
car latimeout
commande ne démarrera qu'une fois la substitution de commande terminée. Utiliseztimeout 1 sh -c 'while true; do echo "GNU">>file2; done'
.write(2)
appels système, et non aux charges de bateaux d'autres appels système, à la surcharge du shell ou même à la création de processus dans votre tout premier exemple (qui s'exécute et attenddate
pour chaque ligne imprimée dans le fichier). Une seconde d’écriture suffit à peine à goulot d’étranglement sur les E / S du disque (plutôt que sur le processeur / la mémoire), sur un système moderne disposant de beaucoup de RAM. Si on laisse courir plus longtemps, la différence serait moins grande. (En fonction de la qualité de votre implémentation bash et de la vitesse relative du processeur et du disque, il est possible que vous ne saturiez même pas les E / S du disque avec bash).Réponses:
coquille de noix:
yes
présente un comportement similaire à la plupart des autres utilitaires standard qui écrivent généralement dans un FILE STREAM avec la sortie tamponnée par la bibliothèque libC via stdio . Ceux-ci ne font que l'appel systèmewrite()
tous les 4 ko (16 ko ou 64 ko) ou quel que soit le bloc de sortie BUFSIZ .echo
est unwrite()
perGNU
. C'est beaucoup de changement de mode (ce qui, apparemment, n'est pas aussi coûteux qu'un changement de contexte ) .Et cela ne
yes
veut pas du tout dire que, outre sa boucle d’optimisation initiale, il s’agit d’une très simple, petite boucle C compilée et que votre boucle shell n’est en aucun cas comparable à un programme optimisé pour le compilateur.Mais je me trompais:
Quand j'ai dit auparavant que
yes
stdio était utilisé, je pensais que c'était le cas, car il se comporte beaucoup comme ceux qui le font. Ce n'était pas correct - cela ne fait que simuler leur comportement. Ce qu’il fait en réalité ressemble beaucoup à ce que j’ai fait ci-dessous avec le shell: il commence par boucler la fusion de ses arguments (ouy
s’il n’en a pas un) jusqu’à ce qu’ils ne puissent plus grandir sans dépasserBUFSIZ
.Un commentaire de la source précédant immédiatement la
for
boucle en question indique:yes
fait sa fait sa proprewrite()
s par la suite.digression:
(Tel qu'initialement inclus dans la question et retenu comme contexte pour une explication éventuellement informative déjà écrite ici) :
Le
timeout
problème que vous avez avec la substitution de commande - je pense que je l’ai compris maintenant, et peut expliquer pourquoi cela ne s’arrête pas.timeout
ne démarre pas car sa ligne de commande n'est jamais exécutée. Votre shell fourche un shell enfant, ouvre un tuyau sur sa sortie standard et le lit. Il arrêtera de lire lorsque l'enfant quittera, puis interprétera tout ce qu'il a écrit pour les$IFS
extensions globales et globales, et avec les résultats, il remplacera tout, de$(
la correspondance)
.Mais si l'enfant est une boucle sans fin qui n'a jamais écrit à la conduite, l'enfant ne cesse jamais en boucle, et
timeout
est jamais terminé commande en ligne de » avant (que je suppose) que vous faitesCTRL-C
et tuer la boucle de l' enfant. Donc, netimeout
peut jamais tuer la boucle qui doit se terminer avant de pouvoir commencer.autres
timeout
s:... ne sont tout simplement pas aussi pertinents pour vos problèmes de performances que le temps que votre programme shell doit passer de la commutation entre le mode utilisateur et le mode noyau pour gérer la sortie.
timeout
Cependant, n’est pas aussi souple qu’un shell peut l’être à cette fin: lorsque les shells excellent, c’est leur capacité à gérer les arguments et à gérer d’autres processus.Comme indiqué ailleurs, le simple fait de déplacer votre
[fd-num] >> named_file
redirection vers la cible de sortie de la boucle plutôt que d'y diriger uniquement la sortie pour la commande en boucle peut considérablement améliorer les performances, car au moins leopen()
syscall ne doit être exécuté qu'une seule fois. Ceci est également fait ci-dessous avec le|
tuyau ciblé comme sortie pour les boucles internes.comparaison directe:
Vous pourriez faire comme:
Ce qui est un peu comme la relation de commande décrite précédemment, mais il n'y a pas de canal et l'enfant est en arrière-plan jusqu'à ce qu'il tue le parent. Dans le
yes
cas où le parent a effectivement été remplacé depuis la génération de l'enfant, mais le shell appelleyes
en superposant son propre processus avec le nouveau et le PID reste le même et son enfant zombie sait toujours qui tuer après tout.plus grand tampon:
Voyons maintenant comment augmenter la
write()
mémoire tampon du shell .J'ai choisi ce nombre parce que les chaînes de sortie de plus de 1 Ko étaient séparées
write()
pour moi. Et voici donc à nouveau la boucle:Cela représente 300 fois la quantité de données écrites par le shell dans le même laps de temps pour ce test que pour le dernier. Pas trop mal. Mais ce n'est pas
yes
.apparenté, relié, connexe:
Comme demandé, il existe une description plus détaillée que les simples commentaires de code sur ce qui est fait ici sur ce lien .
la source
dd
est un outil standard qui n'utilise certainement pas stdio, par exemple. la plupart des autres le font.open
(exister)write
ANDclose
(ce qui, je crois, attend toujours le flush), ET de créer un nouveau processus et de l'exécuterdate
, pour chaque boucle.wc -l
boucle avecbash
moi obtient 1 / 5ème de la sortie de lash
boucle -bash
gère un peu plus de 100kwrites()
àdash
500k.for((sec0=`date +%S`;...
contrôle de l'heure et la redirection dans la boucle, et non les améliorations ultérieures.Une meilleure question serait de savoir pourquoi votre shell écrit le fichier si lentement. Tout programme compilé autonome qui utilise les appels système de manière responsable (sans effacer tous les caractères à la fois) le ferait raisonnablement rapidement. Ce que vous faites, c'est écrire des lignes dans un langage interprété (le shell), et en plus, vous effectuez beaucoup d'opérations de sortie d'entrée inutiles. Qu'est
yes
- ce que:Qu'est-ce que votre script fait:
date
commande externe et stockez sa sortie (uniquement dans la version d'origine - dans la version révisée, vous obtenez un facteur 10 en ne le faisant pas)echo
commande, la reconnaître (avec un code de correspondance de modèle) comme un shell intégré, appeler le développement des paramètres et tout le reste sur l'argument "GNU", et enfin écrire la ligne dans le fichier ouvertLes parties coûteuses: toute l’interprétation est extrêmement coûteuse (bash effectue énormément de pré-traitement de toutes les entrées - votre chaîne pourrait potentiellement contenir une substitution de variable, une substitution de processus, une extension d’accolade, des caractères d’échappement, etc.), chaque appel d’une commande intégrée est probablement une instruction switch avec redirection vers une fonction qui traite de la fonction intégrée, et très important, vous ouvrez et fermez un fichier pour chaque ligne de sortie. Vous pourriez mettre
>> file
hors de la boucle while pour le rendre beaucoup plus rapide , mais vous êtes toujours dans un langage interprété. Vous avez de la chanceecho
est un shell intégré, pas une commande externe - sinon, votre boucle impliquerait la création d'un nouveau processus (fork & exec) à chaque itération. Ce qui aurait paralysé le processus - vous avez vu à quel point c'était coûteux lorsque vous aviez ladate
commande dans la boucle.la source
Les autres réponses ont abordé les points principaux. En passant, vous pouvez augmenter le débit de votre boucle while en écrivant dans le fichier de sortie à la fin du calcul. Comparer:
avec
la source