Wie kann ich diesen Unix-Befehl optimieren?

Wie kann ich diesen Unix-Befehl optimieren?

Der folgende Befehl benötigt etwa 10 Minuten, um das Ergebnis auszugeben

find . -name "muc*_*_20160920_*.unl*" | xargs zcat |
    awk -F "|" '{if($14=="20160920100643" && $22=="567094398953") print $0}'| head

Wie kann ich die Leistung verbessern?

Antwort1

Das ist schon ziemlich optimiert. Es ist schwer zu wissen, wo der Flaschenhals liegt, ohne weitere Details zu kennen, wie:

  • Speichertyp (HD, SSD, Netzwerk, RAID)
  • Anzahl und durchschnittliche Größe der übereinstimmenden Dateien
  • Anzahl der Verzeichnisse und anderer nicht übereinstimmender Dateien
  • Anzahl der Felder in jeder Zeile
  • durchschnittliche Länge einer Zeile

Was Sie auf jeden Fall tun können:

  • Ersetzen Sie es -print | xargsdurch -exec cmd {} +oder, -print0 | xargs -r0wenn Ihr find/ xargses unterstützt. -print | xargsist nicht nur falsch, sondern auch teurer, da xargsZeichen dekodiert werden müssen, um herauszufinden, welche Leerzeichen sind, und eine teure Anführungszeichenverarbeitung durchgeführt werden muss.
  • fixieren Sie das Gebietsschema auf C ( export LC_ALL=C). Da alle hier beteiligten Zeichen ( und Dezimalstellen für den Dateiinhalt und lateinische Buchstaben, Punkt und Unterstrich für die Dateinamen) Teil des portablen Zeichensatzes sind, wird Ihnen und |eine Menge Arbeit ersparen, wenn Sie auf C mit seinem Einzelbyte-Zeichensatz umstellen, falls Ihr Zeichensatz ansonsten UTF-8 oder ein anderer Mehrbyte-Zeichensatz ist .findawk
  • Vereinfachen Sie den awkTeil zu: awk -F "|" '$14 == "20160920100643" && $22 == "567094398953"'.
  • da Sie die Ausgabe an weiterleiten head, möchten Sie möglicherweise die Ausgabepufferung für deaktivieren, awkdamit diese 10 Zeilen so früh wie möglich ausgegeben werden. Mit gawkoder mawkkönnen Sie fflush()dafür verwenden. Oder Sie könnten ein if (++n == 10) exitin hinzufügen awk.

Um zusammenzufassen:

(export LC_ALL=C
find . -name "muc*_*_20160920_*.unl*" -exec zcat {} + |
  awk -F "|" '$14 == "20160920100643" && $22 == "567094398953" {
    print; if (++n == 10) exit}')

Wenn die CPU der Engpass ist, können Sie auf einem Multi-Core-GNU-System Folgendes versuchen:

(export LC_ALL=C
find . -name "muc*_*_20160920_*.unl*" -print0 |
  xargs -r0P 4 -n 100 sh -c '
    zcat "$@" | 
      awk -F "|" "\$14 == "20160920100643" && \$22 == "567094398953" {
        print; fflush()}"' sh | head)

zcat | awkUm 4 Jobs parallel auf 100 Dateistapeln auszuführen .

Wenn es 20160920100643sich dabei um einen Zeitstempel handelt, möchten Sie möglicherweise Dateien ausschließen, die zuletzt davor geändert wurden. Bei GNU oder BSD findfügen Sie einen hinzu -newermt '2016-09-20 10:06:42'.

awkWenn Zeilen eine große Anzahl von Feldern haben, wird Ihnen für das Aufteilen und Zuweisen einer bestimmten Anzahl von Feldern eine Strafe auferlegt $n. Ein Ansatz, der nur die ersten 22 Felder berücksichtigt, könnte die Dinge beschleunigen:

grep -E '^([^|]*\|){13}20160920100643(\|[^|]*){7}\|567094398953(\||$)'

anstelle des awkBefehls. Fügen Sie mit GNU grepdie --line-bufferedOption hinzu, die Zeilen beim parallelen Ansatz so früh wie möglich auszugeben oder -m 10beim nicht-parallelen Ansatz nach 10 Übereinstimmungen anzuhalten.

Zusammenfassend lässt sich sagen, wenn die CPU der Flaschenhals ist und Sie über mindestens 4 CPU-Kerne auf Ihrem System verfügen, mindestens 400 muc*-Dateien vorhanden sind und Sie ein GNU-System verwenden (das grepnormalerweise erheblich schneller ist als GNU awk):

(export LC_ALL=C
find . -name "muc*_*_20160920_*.unl*" -newermt '2016-09-20 10:06:42' -print0 |
  xargs -r0P 4 -n 100 sh -c '
    zcat "$@" | 
      grep --line-buffered -E \
        "^([^|]*\|){13}20160920100643(\|[^|]*){7}\|567094398953(\||$)"
  ' sh | head)

Beachten Sie, dass die Ausgabe der grepBefehle beim parallelen Ansatz möglicherweise vermischt ist (bei Zeilenpufferung und vorausgesetzt, die Zeilen sind weniger als ein paar Kilobyte groß, sollten die Zeilengrenzen jedoch erhalten bleiben).

Antwort2

Die Antwort von @Stéphane Chazelas enthält zahlreiche Details dazu, wie Sie die Befehlspipeline optimieren können

find . -name "muc*_*_20160920_*.unl*" | xargs zcat |
    awk -F "|" '{if($14=="20160920100643" && $22=="567094398953") print $0}'| head

Ich werde Ihnen einen anderen Ansatz für das Problem vorstellen, bei dem Sie tatsächlich messen, wo Sie die meiste Zeit verbringen. Sobald Sie herausgefunden haben, wo die meiste Zeit verbracht wird, können Sie bestimmen, was Sie dagegen tun können. Wenn Sie Ihre 10-minütige Laufzeit verbessern möchten, ist die Optimierung eines Schritts, der 2 Sekunden dauert, fast nutzlos.

Wenn ich mir die Befehlspipeline anschaue, fallen mir drei Dinge auf:

  1. find .- Wie sieht die Verzeichnisstruktur aus? Wie viele Dateien sind pro Verzeichnis? Ist das Verzeichnis lokal auf dem System, auf dem der Befehl ausgeführt wird? Ein Remote-Dateisystem wird einvielLangsamer.
  2. -name "muc*_*_20160920_*.unl*"- Wie ähnlich sind sich alle Dateinamen in der Verzeichnisstruktur? Sind sie alle „nahe“ am Namen und schwierig/CPU-intensiv zuzuordnen? WeiljedenDer Name der Datei im Verzeichnisbaum muss von der Festplatte gelesen und mit dem Muster verglichen werden.
  3. xargs zcat- Das xargsscheint mir kein allzu großes Leistungsproblem zu sein, insbesondere im Vergleich zu den findoben genannten Problemen und dem zcatselbst. Selbst wenn es 10.000 oder sogar 10.000.000 Dateinamen sind, ist die Zeit, die zum Übergeben und Analysieren nur der Namen benötigt wird, im Vergleich zu der Zeit, die aufgewendet wird, fast sicher vernachlässigbarfindendie Namen und dann alle Dateien selbst öffnen und dekomprimieren. Wie groß sind die Dateien? Weil Sie die Gesamtheit dekomprimierenjedenDatei, die mit Ihrem findDateinamenmuster übereinstimmt.

Wie können Sie das größte Leistungsproblem ermitteln? Messen Sie die Leistung jedes Befehls in der Pipeline. (Siehehttps://stackoverflow.com/questions/13294554/wie-man-gnu-time-mit-pipeline-verwendetfür Details zum Timing einer gesamten Pipeline.) Sie können die folgenden Befehle ausführen und sehen, wie viel Zeit jeder Schritt zur Verarbeitungszeit für die gesamte Pipeline beiträgt:

/usr/bin/time find .- Hier erfahren Sie, wie lange es dauert, Ihren Verzeichnisbaum zu durchlaufen. Wenn dies langsam ist, benötigen Sie ein besseres Speichersystem. Leeren Sie den Cache Ihres DateisystemsBevor Sie dies zeitlich festlegen, um eine Worst-Case-Messung zu erhalten, führen Sie die Zeitmessung finderneut aus und prüfen Sie, wie stark sich das Caching auf die Leistung auswirkt. Und wenn das Verzeichnis nicht lokal ist, versuchen Sie, den Befehl auf dem tatsächlichen System auszuführen, auf dem sich die Dateien befinden.

/usr/bin/time find . -name "muc*_*_20160920_*.unl*"- Hier erfahren Sie, wie lange es dauert, die Muster der Dateinamen zu vergleichen. Leeren Sie erneut den Dateisystem-Cache und führen Sie ihn zweimal aus.

/usr/bin/time bash -c "find . -name 'muc*_*_20160920_*.unl*' | xargs zcat > /dev/null"- Ich vermute, dass dies der Hauptgrund für die lange Laufzeit Ihrer Pipeline ist. Wenn dies das Problem ist, zcatist die Parallelisierung der Befehle gemäß der Antwort von Stéphane Chazelas möglicherweise die beste Lösung.

Fügen Sie weitere Schritte aus der ursprünglichen Befehlspipeline zu der zu testenden hinzu, bis Sie herausfinden, wo Sie die meiste Zeit verbringen. Auch hier vermute ich, dass es am zcatSchritt liegt. Wenn ja, zcathilft vielleicht die von @Stéphane Chazelas gepostete Parallelisierung.

Parallelisierung zcathilft möglicherweise nicht - sie kann sogarverletztLeistung und langsame Verarbeitung. Wenn nur ein Prozess zcatgleichzeitig ausgeführt wird, kann die IO in einem guten Streaming-Muster erfolgen, das die Festplattensuchvorgänge minimiert. Wenn mehrere zcatProzesse gleichzeitig ausgeführt werden, können die IO-Operationen miteinander konkurrieren und die Verarbeitung tatsächlich verlangsamen, da die Festplattenköpfe suchen müssen und das Vorauslesen weniger effektiv wird.

Wenn der zcatSchritt Ihren größten Leistungsengpass darstellt und das zcatgleichzeitige Ausführen mehrerer Prozesse nicht hilft oder Sie sogar verlangsamt, ist Ihre Pipeline IO-gebunden und Sie müssen das Problem durch die Verwendung schnellerer Speicher beheben.

Und noch einmal: Wenn sich das Verzeichnis nicht lokal auf der Maschine befindet, auf der Sie die Befehlspipeline ausführen, versuchen Sie, sie auf der Maschine auszuführen, auf der sich das Dateisystem tatsächlich befindet.

Antwort3

Wie in den Kommentaren erwähntzgrepist die bessere Wahl für solche Aufgaben mitWeltstarOption, die die Verwendung **alsall path inside the directory except hidden

shopt -s globstar
zgrep -m 10 '^\([^|]*|\)\{13\}20160920100643|\([^|]*|\)\{7\}567094398953' ./**muc*_*_20160920_*.unl*
shopt -u globstar

Antwort4

Wie bereits erwähnt, ist es ohne zusätzliche Einzelheiten nicht möglich, die richtige Antwort zu geben.

locate -0 -b -r '^muc.*_.*_20160920_.*.unl.*gz' | 
   xargs -0  zcat |
   awk -F "|" '$14=="20160920100643" && $22=="567094398953"'| head
  • 1: Locate (falls verfügbar) ist viel schneller als **oder find; Der verwendete reguläre Ausdruck muss angepasst werden …

  • 2 und 3: der Filter des PO

Wie @rudimeier richtigerweise anmerkte, gibt es Probleme hinsichtlich der Verfügbarkeit und des Aktualisierungsstatus von locate. (Beispiel: Auf den meisten Linux-Rechnern wird „locate“ täglich aktualisiert; daher kann es heute erstellte Dateien nicht finden.)

Wenn die Ortung jedoch möglich ist, führt dies zu einer erheblichen Beschleunigung.

Es wäre interessant, wenn die PO time ...die verschiedenen Lösungen bereitstellen könnte

verwandte Informationen