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
$ wc -l bigfile.log
1617000 bigfile.log
Scripts
xtian@clafujiu:~/tmp$ cat p1.sh
tail -n 1000000 bigfile.log | grep '"success": true' | wc -l
tail -n 1000000 bigfile.log | grep '"success": false' | wc -l
xtian@clafujiu:~/tmp$ cat p2.sh
log=$(tail -n 1000000 bigfile.log)
echo "$log" | grep '"success": true' | wc -l
echo "$log" | grep '"success": true' | wc -l
Je n'ai pas de correspondance pour le grep, donc rien n'est écrit dans le dernier tube wc -l
Voici les horaires:
xtian@clafujiu:~/tmp$ time bash p1.sh
0
0
real 0m0.381s
user 0m0.248s
sys 0m0.280s
xtian@clafujiu:~/tmp$ time bash p2.sh
0
0
real 0m46.060s
user 0m43.903s
sys 0m2.176s
J'ai donc exécuté à nouveau les deux scripts via la commande strace
strace -cfo p1.strace bash p1.sh
strace -cfo p2.strace bash p2.sh
Voici les résultats des traces:
$ cat p1.strace
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
97.24 0.508109 63514 8 2 waitpid
1.61 0.008388 0 84569 read
1.08 0.005659 0 42448 write
0.06 0.000328 0 21233 _llseek
0.00 0.000024 0 204 146 stat64
0.00 0.000017 0 137 fstat64
0.00 0.000000 0 283 149 open
0.00 0.000000 0 180 8 close
...
0.00 0.000000 0 162 mmap2
0.00 0.000000 0 29 getuid32
0.00 0.000000 0 29 getgid32
0.00 0.000000 0 29 geteuid32
0.00 0.000000 0 29 getegid32
0.00 0.000000 0 3 1 fcntl64
0.00 0.000000 0 7 set_thread_area
------ ----------- ----------- --------- --------- ----------------
100.00 0.522525 149618 332 total
Et p2.strace
$ cat p2.strace
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
75.27 1.336886 133689 10 3 waitpid
13.36 0.237266 11 21231 write
4.65 0.082527 1115 74 brk
2.48 0.044000 7333 6 execve
2.31 0.040998 5857 7 clone
1.91 0.033965 0 705681 read
0.02 0.000376 0 10619 _llseek
0.00 0.000000 0 248 132 open
...
0.00 0.000000 0 141 mmap2
0.00 0.000000 0 176 126 stat64
0.00 0.000000 0 118 fstat64
0.00 0.000000 0 25 getuid32
0.00 0.000000 0 25 getgid32
0.00 0.000000 0 25 geteuid32
0.00 0.000000 0 25 getegid32
0.00 0.000000 0 3 1 fcntl64
0.00 0.000000 0 6 set_thread_area
------ ----------- ----------- --------- --------- ----------------
100.00 1.776018 738827 293 total
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 dans grep
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
et execv
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 probablement memory 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 fois wc -l
, ou mieux encore, de awk
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.