Je compare ce qui suit
tail -n 1000000 stdout.log | grep -c '"success": true'
tail -n 1000000 stdout.log | grep -c '"success": false'
avec ce qui suit
log=$(tail -n 1000000 stdout.log)
echo "$log" | grep -c '"success": true'
echo "$log" | grep -c '"success": false'
et étonnamment, la seconde prend presque 3 fois plus de temps que la première. Ça devrait être plus rapide, non?
bash
performance
io
phunehehe
la source
la source
$( command substitution )
n'est pas diffusé. Tout le reste se produit via des canaux simultanément, mais dans le deuxième exemple, vous devez attendre lalog=
fin de l'opération. Essayez-le avec << ICI \ n $ {log = $ (commande)} \ n ICI - voyez ce que vous obtenez.grep
utiliser, vous pouvez constater une accélération detee
sorte que le fichier ne soit définitivement lu qu'une seule fois.cat stdout.log | tee >/dev/null >(grep -c 'true'>true.cnt) >(grep -c 'false'>false.cnt); cat true.cnt; cat false.cnt
tail -n 10000 | fgrep -c '"success": true'
et faux.Réponses:
D'une part, la première méthode appelle
tail
deux fois, elle doit donc faire plus de travail que la deuxième méthode qui ne le fait qu'une seule fois. D'un autre côté, la deuxième méthode doit copier les données dans le shell, puis revenir en arrière, elle doit donc faire plus de travail que la première version où elletail
est directement canaliséegrep
. La première méthode a un avantage supplémentaire sur une machine multiprocesseur:grep
peut fonctionner en parallèle avectail
, alors que la deuxième méthode est strictement sérialisée, d'abordtail
, ensuitegrep
.Il n'y a donc aucune raison évidente pour laquelle l'un devrait être plus rapide que l'autre.
Si vous voulez voir ce qui se passe, regardez ce que le système appelle le shell. Essayez également avec différentes coquilles.
Avec la méthode 1, les principales étapes sont:
tail
lit et cherche à trouver son point de départ.tail
écrit des morceaux de 4096 octets quigrep
lisent aussi vite qu'ils sont produits.Avec la méthode 2, les principales étapes sont:
tail
lit et cherche à trouver son point de départ.tail
écrit des blocs de 4096 octets qui bash lit 128 octets à la fois, et zsh lit 4096 octets à la fois.grep
lisent aussi vite qu'ils sont produits.Les morceaux de 128 octets de Bash lors de la lecture de la sortie de la substitution de commandes la ralentissent considérablement; zsh sort à peu près aussi vite que la méthode 1 pour moi. Votre kilométrage peut varier en fonction du type et du nombre de CPU, de la configuration du planificateur, des versions des outils impliqués et de la taille des données.
la source
st_blksize
valeur d'un tuyau, qui est 4096 sur cette machine (et je ne sais pas si c'est parce que c'est la taille de la page MMU). Le 128 de Bash devrait être une constante intégrée.J'ai fait le test suivant et sur mon système, la différence résultante est environ 100 fois plus longue pour le deuxième script.
Mon fichier est une sortie strace appelée
bigfile
Scripts
Je n'ai pas de correspondance pour le grep, donc rien n'est écrit dans le dernier tube
wc -l
Voici les horaires:
J'ai donc exécuté à nouveau les deux scripts via la commande strace
Voici les résultats des traces:
Et p2.strace
Une analyse
Sans surprise, dans les deux cas, la plupart du temps est passé à attendre la fin d'un processus, mais p2 attend 2,63 fois plus longtemps que p1, et comme d'autres l'ont mentionné, vous commencez tard dans p2.sh.
Alors maintenant oubliez le
waitpid
, ignorez la%
colonne et regardez la colonne des secondes sur les deux traces.Le plus grand temps que p1 passe la plupart de son temps en lecture est probablement compréhensible, car il y a un gros fichier à lire, mais p2 passe 28,82 fois plus en lecture que p1. -
bash
ne s'attend pas à lire un fichier aussi volumineux dans une variable et lit probablement le tampon à la fois, se divise en lignes, puis en obtient un autre.le nombre de lectures p2 est de 705k contre 84k pour p1, chaque lecture nécessitant un changement de contexte dans l'espace du noyau et à nouveau. Près de 10 fois le nombre de lectures et de changements de contexte.
Le temps d'écriture p2 passe 41,93 fois plus longtemps en écriture que p1
le nombre d'écritures p1 fait plus d'écrits que p2, 42k vs 21k, mais ils sont beaucoup plus rapides.
Probablement à cause des
echo
lignes dansgrep
par opposition aux tampons d'écriture de queue.De plus , p2 passe plus de temps en écriture qu'en lecture, p1 est l'inverse!
Autre facteur Regardez le nombre d'
brk
appels système: p2 passe 2,42 fois plus de temps qu'il ne le fait à lire! En p1 (il ne s'enregistre même pas).brk
est lorsque le programme a besoin d'étendre son espace d'adressage car suffisamment n'a pas été alloué initialement, cela est probablement dû au fait que bash doit lire ce fichier dans la variable, et ne s'attend pas à ce qu'il soit si grand, et comme @scai l'a mentionné, si le le fichier devient trop volumineux, même cela ne fonctionnerait pas.tail
est probablement un lecteur de fichiers assez efficace, car c'est ce pour quoi il a été conçu, il mappe probablement le fichier et recherche les sauts de ligne, permettant ainsi au noyau d'optimiser les E / S. bash n'est pas aussi bon en termes de temps passé à lire et à écrire.p2 passe 44 ms et 41 ms
clone
etexecv
ce n'est pas une quantité mesurable pour p1. Probablement lire bash et créer la variable à partir de la queue.Enfin, le Totals p1 exécute ~ 150k appels système vs p2 740k (4,93 fois plus).
En éliminant waitpid, p1 passe 0,014416 secondes à exécuter des appels système, p2 0,439132 secondes (30 fois plus).
Il semble donc que p2 passe la plupart du temps dans l'espace utilisateur à ne rien faire, sauf à attendre que les appels système se terminent et que le noyau réorganise la mémoire, p1 effectue plus d'écritures, mais est plus efficace et entraîne une charge système nettement inférieure, et est donc plus rapide.
Conclusion
Je n'essaierais jamais de me soucier du codage via la mémoire lors de l'écriture d'un script bash, cela ne veut pas dire que vous n'essayez pas d'être efficace.
tail
est conçu pour faire ce qu'il fait, c'est probablementmemory maps
le fichier pour qu'il soit efficace à lire et permette au noyau d'optimiser les E / S.Une meilleure façon d'optimiser votre problème pourrait être de commencer
grep
par les lignes de «succès»: puis de compter les vrais et les faux,grep
a une option de comptage qui évite encore une foiswc -l
, ou mieux encore, deawk
diriger la queue vers et de compter les vraies et fausses simultanément. p2 non seulement prend beaucoup de temps mais ajoute de la charge au système pendant que la mémoire est mélangée avec brks.la source
En fait, la première solution lit également le fichier en mémoire! Cela s'appelle la mise en cache et est effectuée automatiquement par le système d'exploitation.
Et comme déjà correctement expliqué par mikeserv, la première solution s'exécute
grep
pendant la lecture du fichier tandis que la seconde solution l'exécute après la lecture du fichiertail
.La première solution est donc plus rapide en raison de diverses optimisations. Mais cela ne doit pas toujours être vrai. Pour les très gros fichiers que le système d'exploitation décide de ne pas mettre en cache, la deuxième solution pourrait devenir plus rapide. Mais notez que pour des fichiers encore plus volumineux qui ne rentrent pas dans votre mémoire, la deuxième solution ne fonctionnera pas du tout.
la source
Je pense que la principale différence est très simplement que
echo
c'est lent. Considère ceci:Comme vous pouvez le voir ci-dessus, l'étape qui prend du temps est l'impression des données. Si vous redirigez simplement vers un nouveau fichier et effectuez une grep, cela est beaucoup plus rapide lorsque vous ne lisez le fichier qu'une seule fois.
Et comme demandé, avec une chaîne ici:
Celui-ci est encore plus lent, probablement parce que la chaîne ici concatène toutes les données sur une longue ligne et cela ralentira
grep
:Si la variable est citée de façon à ce qu'aucun fractionnement ne se produise, les choses sont un peu plus rapides:
Mais toujours lent car l'étape de limitation de débit imprime les données.
la source
<<<
il serait intéressant de voir si cela fait une différence.J'ai aussi essayé ça ... Tout d'abord, j'ai construit le fichier:
Si vous exécutez ce qui précède vous-même, vous devriez trouver 1,5 million de lignes
/tmp/log
avec un rapport 2: 1 de"success": "true"
lignes à"success": "false"
lignes.La prochaine chose que j'ai faite a été de faire des tests. J'ai exécuté tous les tests via un proxy
sh
,time
je n'aurais donc qu'à regarder un seul processus - et pourrais donc afficher un seul résultat pour l'ensemble du travail.Cela semble être le plus rapide, même s'il ajoute un deuxième descripteur de fichier et
tee,
bien que je pense pouvoir expliquer pourquoi:Voici votre premier:
Et votre deuxième:
Vous pouvez voir que dans mes tests, il y avait plus d'une différence de vitesse de 3 * lors de la lecture dans une variable comme vous l'avez fait.
Je pense qu'une partie de cela est qu'une variable shell doit être divisée et gérée par le shell lors de sa lecture - ce n'est pas un fichier.
A
here-document
d'autre part, à toutes fins utiles, est unfile
- un defile descriptor,
toute façon. Et comme nous le savons tous, Unix fonctionne avec des fichiers.Ce qui m'intéresse le plus,
here-docs
c'est que vous pouvez manipuler leurfile-descriptors
- comme une ligne droite|pipe
- et les exécuter. Ceci est très pratique car il vous permet un peu plus de liberté pour pointer|pipe
où vous le souhaitez.Je devais
tee
letail
car le premiergrep
mange lehere-doc |pipe
et il n'y a plus rien à lire pour le second. Mais depuis que je|piped
dans/dev/fd/3
et pris à nouveau passer à>&1 stdout,
elle n'a pas beaucoup d' importance. Si vous en utilisezgrep -c
autant que d'autres recommandent:C'est encore plus rapide.
Mais quand je le lance sans
. sourcing
leheredoc
je ne peux fond avec succès le premier processus pour les exécuter entièrement en même temps. Le voici sans le fonder complètement:Mais quand j'ajoute le
&:
Pourtant, la différence ne semble être que de quelques centièmes de seconde, du moins pour moi, alors prenez-la comme vous voulez.
Quoi qu'il en soit, la raison pour laquelle il s'exécute plus rapidement
tee
est parce que les deuxgreps
s'exécutent en même temps avec une seule invocation detail. tee
doublons le fichier pour nous et le divise au deuxièmegrep
processus tout en continu - tout s'exécute en même temps du début à la fin, donc ils tous finissent à peu près au même moment aussi.Revenons donc à votre premier exemple:
Et votre deuxième:
Mais lorsque nous divisons notre entrée et exécutons nos processus simultanément:
la source