Ich habe den folgenden Test durchgeführt und auf meinem System ist der resultierende Unterschied für das zweite Skript ungefähr 100-mal länger.
Meine Datei wird als Strace-Ausgabe bezeichnet bigfile
$ wc -l bigfile.log
1617000 bigfile.log
Skripte
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
Ich habe eigentlich keine Übereinstimmungen für das grep, also wird nichts bis zur letzten Pipe durchgeschrieben wc -l
Hier sind die Zeiten:
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
Also habe ich die beiden Skripte erneut über den Befehl strace ausgeführt
strace -cfo p1.strace bash p1.sh
strace -cfo p2.strace bash p2.sh
Hier sind die Ergebnisse aus den Spuren:
$ 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
Und 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
Analyse
Es überrascht nicht, dass in beiden Fällen der größte Teil der Zeit damit verbracht wird, auf den Abschluss eines Prozesses zu warten, aber p2 wartet 2,63-mal länger als p1, und wie andere bereits erwähnt haben, beginnen Sie spät in p2.sh.
Vergessen Sie also das waitpid
, ignorieren Sie die %
Spalte und sehen Sie sich die Sekunden-Spalte auf beiden Spuren an.
Größte Zeit p1 verbringt die meiste Zeit beim Lesen wahrscheinlich verständlicherweise, da es eine große zu lesende Datei gibt, aber p2 verbringt 28,82-mal mehr Zeit beim Lesen als p1. - bash
erwartet nicht, dass eine so große Datei in eine Variable eingelesen wird, und liest wahrscheinlich jeweils einen Puffer, teilt sich in Zeilen auf und holt sich dann eine andere.
Die Lesezahl p2 beträgt 705k gegenüber 84k für p1, wobei jeder Lesevorgang eine Kontextumschaltung in den Kernelraum und wieder zurück erfordert. Fast zehnmal so viele Lesevorgänge und Kontextwechsel.
Die Schreibzeit für p2 ist 41,93-mal länger als für p1
write count p1 schreibt mehr als p2, 42k vs 21k, jedoch sind sie viel schneller.
Wahrscheinlich wegen der echo
von Zeilen in grep
Schreibpuffern im Gegensatz zu Schwanz.
Außerdem verbringt p2 beim Schreiben mehr Zeit als beim Lesen, p1 ist umgekehrt!
Anderer Faktor Schauen Sie sich die Anzahl der brk
Systemaufrufe an: p2 verbringt 2,42-mal mehr Zeit mit dem Brechen als beim Lesen! In p1 (es registriert nicht einmal). brk
Wenn das Programm seinen Adressraum erweitern muss, weil anfangs nicht genug zugewiesen wurde, liegt dies wahrscheinlich daran, dass Bash diese Datei in die Variable einlesen muss und nicht erwartet, dass sie so groß ist, und wie @scai erwähnt, wenn die Datei wird zu groß, auch das würde nicht funktionieren.
tail
ist wahrscheinlich ein recht effizienter Dateireader, da er genau dafür entwickelt wurde, die Datei zu speichern und nach Zeilenumbrüchen zu suchen, sodass der Kernel die Ein- / Ausgabe optimieren kann. Bash ist nicht so gut, sowohl beim Lesen als auch beim Schreiben.
p2 verbringt 44ms und 41ms in clone
und execv
es ist kein messbarer Betrag für p1. Vermutlich bash das Lesen und Erzeugen der Variablen aus dem Schwanz.
Schließlich führt die Gesamtsumme p1 ~ 150.000 Systemaufrufe gegenüber p2 740.000 (4,93-mal höher) aus.
Wenn Sie waitpid eliminieren, verbringt p1 0,014416 Sekunden mit der Ausführung von Systemaufrufen, p2 0,439132 Sekunden (30-mal länger).
Daher verbringt p2 die meiste Zeit im Benutzerbereich mit nichts anderem, als darauf zu warten, dass die Systemaufrufe abgeschlossen sind und der Kernel den Speicher neu organisiert.
Fazit
Ich würde niemals versuchen, mir Gedanken über das Codieren durch den Speicher zu machen, wenn ich ein Bash-Skript schreibe. Das bedeutet nicht, dass Sie nicht versuchen, effizient zu sein.
tail
entwickelt, um zu tun, was es tut, ist es wahrscheinlich memory maps
die Datei, so dass es effizient zu lesen ist und ermöglicht dem Kernel, die I / O zu optimieren.
Ein besserer Weg, um Ihr Problem zu optimieren, könnte darin bestehen, zuerst grep
nach "Erfolg" zu suchen: "Zeilen" und dann nach "Wahr und Falsch" zu zählen. Außerdem grep
gibt es eine Zähloption, mit der das Weiterleiten wc -l
des Schwanzes zu awk
und Zählen von Wahr und Falsch umgangen wird fälscht gleichzeitig. p2 dauert nicht nur lange, sondern belastet auch das System, während der Speicher mit brks gemischt wird.