Gibt es eine Möglichkeit, diesen Einzeiler schneller zu machen?

7

Kontext

Ich habe ein Verzeichnis mit Tausenden von Zip-Dateien, die im Formular datiert sind YYYYMMDD_hhmmss.zipund jeweils etwa 300 KB groß sind. In jeder Zip-Datei befinden sich ca. 400 XML-Dateien mit jeweils ca. 3 KB.

Das Problem

Ich muss in der Lage sein, eine bestimmte Zeichenfolge innerhalb eines Datumsbereichs der Zip-Dateien zu suchen und zu finden.

Die aktuelle (wenn auch mittelmäßige) Lösung

Ich habe den folgenden Einzeiler

find /home/mydir/ -type f | sort | \
awk "/xml_20140207_000016.zip/,/xml_20140207_235938.zip/" | \
xargs -n 1 -P 10 zipgrep "my search string"

Der Punkt ist zu

  1. Listen Sie alle Dateien in meinem Verzeichnis mit tausend Dateien auf
  2. Sortieren Sie diese Liste von Dateien
  3. Abrufen einer Reihe von Dateien basierend auf bestimmten Daten (dieser awkBefehl druckt nur Zeilen nach dieser ersten übereinstimmenden Zeichenfolge und bis zu dieser zweiten übereinstimmenden Zeichenfolge).
  4. Übergeben Sie jede Zeile des Ergebnisses, die einer einzelnen Datei entspricht, an zipgrep

Die Frage

Dieser Einzeiler läuft schrecklich langsam, selbst mit 10 Prozessen auf einer 24-Kern-Maschine. Ich glaube, es ist langsam wegen des zipgrepBefehls, aber ich bin nicht klug genug zu wissen, wie man es verbessert. Ich weiß nicht, ob ich es sein sollte, aber es ist mir ein wenig peinlich, dass ein Kollege ein Java-Tool geschrieben hat, das schneller läuft als dieses Skript. Ich würde das gerne umkehren, wenn es möglich ist. Weiß dann jemand, wie man diesen Befehl in diesem Zusammenhang schneller macht? Oder um irgendeinen Teil davon überhaupt zu verbessern?

Fifosin
quelle
1
Sind Sie sicher, dass die Jahresspanne nur bis zum Jahr 999 reicht? nicht, dass es die Frage viel ändert.
Anthon
4
zipgrep entpackt jede Datei in der Zip-Datei separat, um sie zu erfassen. Dies scheint unwirksam zu sein, wenn Sie sie trotzdem alle betrachten. Vielleicht wird das Entpacken in ein temporäres Verzeichnis und das Durchsuchen dort oder das Herumspielen an der Ausgabe von unzip -poder unzip -ceine kleine Verbesserung bewirken .
Ulrich Schwarz
@UlrichSchwarz Das wusste ich nicht, ich werde es versuchen. Vielen Dank!
Fifosin
Wie hoch ist die Wahrscheinlichkeit, dass die Saite auftritt? Wenn Sie meinen vorherigen Vorschlag erweitern, können Sie zunächst prüfen, unzip -cob die Zip-Datei überhaupt für Ihre Ergebnisse relevant ist, und erst dann die einzelnen darin enthaltenen Dateien genauer untersuchen.
Ulrich Schwarz
Die Wahrscheinlichkeit, dass die Zeichenfolge auftritt, ist nicht hoch, aber auch die archivierten Dateinamen geben keinen Hinweis darauf, was in ihnen enthalten ist.
Fifosin

Antworten:

7

Es gibt einen Teil, den Sie leicht verbessern können, aber es ist nicht der langsamste Teil.

find /home/mydir/ -type f | sort | \
awk "/xml_20140207_000016.zip/,/xml_20140207_235938.zip/"

Dies ist etwas verschwenderisch, da zuerst alle Dateien aufgelistet, dann die Dateinamen sortiert und die interessanten extrahiert werden. Der findBefehl muss vollständig ausgeführt werden, bevor die Sortierung beginnen kann.

Es wäre schneller, zunächst nur die interessanten Dateien aufzulisten oder zumindest eine möglichst kleine Obermenge. Wenn Sie einen feinkörnigeren Filter für Namen benötigen, als dies möglich findist, leiten Sie in awk ein, aber sortieren Sie nicht: awk und andere zeilenweise Filter können Zeilen einzeln verarbeiten, aber die Sortierung erfordert die vollständige Eingabe.

find /home/mydir/ -name 'xml_20140207_??????.zip' -type f | \
awk 'match($0, /_[0-9]*.zip$/) &&
     (time = substr($0, RSTART+1, RLENGTH-5)) &&
     time >= 16 && time <= 235938' |
xargs -n 1 -P 10 zipgrep "my search string"

Der Teil, der am offensichtlichsten suboptimal ist, ist zipgrep. Hier gibt es aufgrund der Einschränkungen der Shell-Programmierung keine einfache Möglichkeit, die Leistung zu verbessern. Das zipgrep-Skript listet die Dateinamen im Archiv auf und ruft grepnacheinander den Inhalt jeder Datei auf. Dies bedeutet, dass das Zip-Archiv für jede Datei immer wieder analysiert wird. Ein Java-Programm (oder Perl oder Python oder Ruby usw.) kann dies vermeiden, indem die Datei nur einmal verarbeitet wird.

Wenn Sie sich an die Shell-Programmierung halten möchten, können Sie versuchen, jede Zip-Datei zu mounten, anstatt zipgrep zu verwenden.

 | xargs -n1 -P2 sh -c '
    mkdir "mnt$$-$1";
    fuse-zip "$1" "mnt$$-$1";
    grep -R "$0" "mnt$$-$1"
    fusermount -u "mnt$$-$1"
' "my search string"

Beachten Sie, dass Parallelität Ihnen nicht viel hilft: Der begrenzende Faktor bei den meisten Setups ist die Festplatten-E / A-Bandbreite und nicht die CPU-Zeit.

Ich habe noch kein Benchmarking durchgeführt, aber ich denke, der größte Verbesserungspotenzial wäre die Verwendung einer Zipgrep-Implementierung in einer leistungsfähigeren Sprache.

Gilles 'SO - hör auf böse zu sein'
quelle
6

Einige schnelle Ideen;

  • Wenn sich alle Dateien in einem einzigen Verzeichnis befinden, können Sie das entfernen find
  • Ihre Dateinamenkonvention sortiert sich nach Datum, sodass Sie das sortBit auch nicht benötigen
  • Wenn diese beiden Teile nicht im Weg sind und der Datumsbereich bekannt ist, können Sie anstelle von awk einen einfachen Dateinamen-Glob verwenden. Zum Beispiel (vorausgesetzt, Ihre Shell ist bash):

    • Alle Dateien eines Tages

      echo xml_20140207_*.zip | xargs -n 1 -P 10 zipgrep "my search string"

    • Dateien, die zwischen 15:00 und 18:00 Uhr erstellt wurden, entweder am 07. Februar oder am 10. Februar 2014:

      echo xml_201402{07,10}_1{5..7}*.zip | xargs -n 1 -P 10 zipgrep "my search string"

x86tux
quelle
Vielen Dank für Ihre Verbesserungen, aber der Bereich, der es am dringendsten benötigt (xargs und zipgrep), bleibt bestehen. Dies sind die Befehle, die den Engpass verursachen. Wie Peter Norvig sagt: "Verschwenden Sie keine Mühe damit, Teile Ihres Programms zu beschleunigen, die nicht viel Zeit in Anspruch nehmen."
Fifosin
3

Es ist nicht klar, wo Ihr Engpass liegt. Nehmen wir an, es ist beim Lesen der Dateien. Abhängig von Ihrem Speichersystem ist es schneller, die gesamte Datei vor der Verarbeitung zu lesen. Dies gilt insbesondere für zipgrepeinige Suchvorgänge in der Datei: Wenn sich die Datei nicht vollständig im Speicher befindet, warten Sie auf die Suche auf der Festplatte.

find ... | parallel -j1 'cat {} >/dev/null; echo {}' | parallel zipgrep "my search string"

Das obige wird jeweils cateine Datei und damit in den Speichercache legen, dann eine zipgreppro CPU ausführen , die dann aus dem Speichercache liest.

Ich habe RAID-Systeme verwendet, bei denen Sie eine 6-fache Geschwindigkeit erzielt haben, indem Sie 10 Dateien parallel gelesen haben, anstatt jeweils 1 Datei oder 30 Dateien parallel zu lesen. Wenn ich die oben auf diesem RAID - System laufen hätte, würde ich einstellen -j1zu -j10.

Wenn Sie stattdessen GNU Parallel verwenden xargs, schützen Sie sich vor dem Mischen der Ausgabe (siehe http://www.gnu.org/software/parallel/man.html#DIFFERENCES-BETWEEN-xargs-AND-GNU-Parallel ).

Ole Tange
quelle