Ich vergleiche folgendes
tail -n 1000000 stdout.log | grep -c '"success": true'
tail -n 1000000 stdout.log | grep -c '"success": false'
mit den folgenden
log=$(tail -n 1000000 stdout.log)
echo "$log" | grep -c '"success": true'
echo "$log" | grep -c '"success": false'
und überraschenderweise dauert die zweite fast dreimal länger als die erste. Es sollte schneller sein, nicht wahr?
bash
performance
io
phunehehe
quelle
quelle
$( command substitution )
heißt nicht gestreamt. Der Rest geschieht gleichzeitig über Pipes, aber im zweiten Beispiel müssen Sie warten, bis der Vorganglog=
abgeschlossen ist. Versuchen Sie es mit << HIER \ n $ {log = $ (Befehl)} \ nHIER - sehen Sie, was Sie bekommen.grep
für die möglicherweise eine Beschleunigung erforderlich ist, wirdtee
die Datei definitiv nur einmal gelesen.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'
und false.Antworten:
Einerseits wird die erste Methode
tail
zweimal aufgerufen, sodass mehr Arbeit als die zweite Methode anfällt, die dies nur einmal ausführt. Auf der anderen Seite muss die zweite Methode die Daten in die Shell kopieren und dann wieder entfernen, sodass sie mehr Arbeit leisten muss als die erste Version, intail
die direkt weitergeleitet wirdgrep
. Das erste Verfahren weist einen zusätzlichen Vorteil auf einer Multi-Prozessor - Maschine:grep
mit parallel arbeitentail
, während die zweiten Methode ist streng serialisiert, zuersttail
, danngrep
.Es gibt also keinen offensichtlichen Grund, warum einer schneller sein sollte als der andere.
Wenn Sie sehen möchten, was los ist, schauen Sie sich an, welches System die Shell aufruft. Versuchen Sie es auch mit verschiedenen Schalen.
Bei Methode 1 sind die Hauptphasen:
tail
liest und sucht seinen Ausgangspunkt zu finden.tail
schreibt 4096-Byte-Chunks, diegrep
so schnell gelesen werden, wie sie produziert werden.Bei Methode 2 sind die Hauptphasen:
tail
liest und sucht seinen Ausgangspunkt zu finden.tail
schreibt 4096-Byte-Chunks, die jeweils 128 Bytes lesen, und zsh liest jeweils 4096 Bytes.grep
so schnell gelesen werden, wie sie produziert werden.Die 128-Byte-Chunks von Bash verlangsamen die Ausgabe der Befehlsersetzung erheblich. zsh kommt für mich genauso schnell raus wie Methode 1. Ihre Laufleistung kann je nach CPU-Typ und -Nummer, Scheduler-Konfiguration, Versionen der beteiligten Tools und Datengröße variieren.
quelle
st_blksize
Wert für eine Pipe, der auf diesem Computer bei 4096 liegt (und ich weiß nicht, ob dies an der MMU-Seitengröße liegt). Bashs 128 müsste eine eingebaute Konstante sein.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
Skripte
Ich habe eigentlich keine Übereinstimmungen für das grep, also wird nichts bis zur letzten Pipe durchgeschrieben
wc -l
Hier sind die Zeiten:
Also habe ich die beiden Skripte erneut über den Befehl strace ausgeführt
Hier sind die Ergebnisse aus den Spuren:
Und p2.strace
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 ingrep
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
undexecv
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 wahrscheinlichmemory 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ßerdemgrep
gibt es eine Zähloption, mit der das Weiterleitenwc -l
des Schwanzes zuawk
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.quelle
Eigentlich liest die erste Lösung die Datei auch in den Speicher! Dies wird als Caching bezeichnet und vom Betriebssystem automatisch durchgeführt.
Und wie von mikeserv bereits richtig erklärt, wird die erste Lösung ausgeführt,
grep
während die Datei gelesen wird, während die zweite Lösung sie ausführt, nachdem die Datei gelesen wurdetail
.Die erste Lösung ist also aufgrund verschiedener Optimierungen schneller. Das muss aber nicht immer wahr sein. Bei sehr großen Dateien, die das Betriebssystem nicht zwischenspeichern möchte, kann die zweite Lösung schneller werden. Beachten Sie jedoch, dass bei noch größeren Dateien, die nicht in Ihren Speicher passen, die zweite Lösung überhaupt nicht funktioniert.
quelle
Ich denke, der Hauptunterschied ist ganz einfach, dass
echo
es langsam ist. Bedenken Sie:Wie Sie oben sehen können, ist der zeitaufwendige Schritt das Drucken der Daten. Wenn Sie einfach zu einer neuen Datei umleiten und diese durchgehen, ist dies viel schneller, wenn Sie die Datei nur einmal lesen.
Und wie gewünscht mit einem Here-String:
Dieser ist sogar noch langsamer, vermutlich, weil der Here-String alle Daten in einer langen Zeile zusammenfasst, und das verlangsamt Folgendes
grep
:Wenn die Variable in Anführungszeichen gesetzt ist, damit keine Aufteilung erfolgt, sind die Dinge etwas schneller:
Aber immer noch langsam, da die Geschwindigkeitsbegrenzung die Daten druckt.
quelle
<<<
es nicht? Es wäre interessant zu sehen, ob das einen Unterschied macht.Ich habe es auch versucht ... Zuerst habe ich die Datei erstellt:
Wenn Sie das oben genannte selbst ausführen, sollten Sie 1,5 Millionen Zeilen
/tmp/log
mit einem Verhältnis von 2: 1 von"success": "true"
Zeilen zu"success": "false"
Zeilen erstellen .Als nächstes habe ich einige Tests durchgeführt. Ich habe alle Tests über einen Proxy durchgeführt, musste
sh
alsotime
nur einen einzigen Prozess überwachen und konnte daher ein einziges Ergebnis für den gesamten Auftrag anzeigen.Dies scheint die schnellste zu sein, obwohl es einen zweiten Dateideskriptor hinzufügt und
tee,
ich denke, ich kann erklären, warum:Hier ist dein erstes:
Und dein zweites:
Sie können sehen, dass es in meinen Tests mehr als 3 * Geschwindigkeitsunterschiede gab, als Sie es in eine Variable eingelesen haben.
Ich denke, ein Teil davon ist, dass eine Shell-Variable geteilt und von der Shell behandelt werden muss, wenn sie gelesen wird - es ist keine Datei.
A
here-document
dagegen ist in jeder Hinsicht einfile
- einfile descriptor,
ohnehin. Und wie wir alle wissen - Unix arbeitet mit Dateien.Was mich am meisten interessiert
here-docs
ist, dass man siefile-descriptors
- als Straight|pipe
- manipulieren und ausführen kann. Dies ist sehr praktisch, da Sie ein wenig mehr Freiheit haben, zu zeigen,|pipe
wohin Sie möchten.Ich musste
tee
die,tail
weil die erste diegrep
issthere-doc |pipe
und die zweite nichts mehr zu lesen hat. Aber da ich|piped
es in/dev/fd/3
und nahm es wieder auf, um>&1 stdout,
es weiterzugeben, machte es nicht viel aus. Wenn Siegrep -c
so viele andere verwenden, empfehlen Sie:Es geht noch schneller.
Aber wenn ich es ohne
. sourcing
das ausführe,heredoc
kann ich den ersten Prozess nicht erfolgreich im Hintergrund ausführen, um sie vollständig gleichzeitig auszuführen. Hier ist es ohne Hintergrund:Aber wenn ich das hinzufüge
&:
Trotzdem scheint der Unterschied, zumindest für mich, nur ein paar Hundertstelsekunden zu betragen. Nehmen Sie ihn also so, wie Sie wollen.
Wie auch immer, der Grund, warum es schneller läuft,
tee
ist, dass beidegreps
gleichzeitig mit nur einem Aufruf vontail. tee
duplicates die Datei für uns ausführen und sie an den zweitengrep
Prozess weiterleiten - alles läuft auf einmal von Anfang bis Ende, so dass sie alle landen auch ungefähr zur selben Zeit.Kehren Sie also zu Ihrem ersten Beispiel zurück:
Und dein zweites:
Aber wenn wir unsere Eingaben aufteilen und unsere Prozesse gleichzeitig ausführen:
quelle