Parallele Ausführung von Pipe-Befehlen

16

Stellen Sie sich das folgende Szenario vor. Ich habe zwei Programme A und B. Programm A-Ausgaben für Standardzeilen von Strings, während Programm B Zeilen von Standard aus verarbeitet. Die Verwendung dieser beiden Programme ist selbstverständlich:

foo @ bar: ~ $ A | B

Jetzt ist mir aufgefallen, dass dies nur einen Kern auffrisst; daher frage ich mich:

Teilen sich die Programme A und B die gleichen Rechenressourcen? Wenn ja, gibt es eine Möglichkeit, A und B gleichzeitig auszuführen?

Eine andere Sache, die mir aufgefallen ist, ist, dass A viel schneller läuft als B, daher frage ich mich, ob ich irgendwie mehr B-Programme ausführen und sie die von A ausgegebenen Zeilen parallel verarbeiten lassen könnte.

Das heißt, A würde seine Zeilen ausgeben, und es würde N Instanzen von Programmen B geben, die diese Zeilen lesen (wer sie zuerst liest), sie verarbeiten und sie auf stdout ausgeben.

Meine letzte Frage lautet also:

Gibt es eine Möglichkeit, die Ausgabe zwischen mehreren B-Prozessen an A weiterzuleiten, ohne auf die Rennbedingungen und andere Inkonsistenzen achten zu müssen, die möglicherweise auftreten könnten?

Jernej
quelle
1
Während dies A | B | Cwie in separaten Prozessen parallel ist, kann es aufgrund der Art der Pipes (B muss auf die Ausgabe von A warten, C muss auf die Ausgabe von B warten) in einigen Fällen immer noch linear sein. Es kommt ganz darauf an, welche Art von Output sie produzieren. Es gibt nicht viele Fälle, in denen das Ausführen von mehreren Bsehr hilfreich ist. Es ist durchaus möglich, dass das parallele wc-Beispiel langsamer als wcnormal ist, da das Aufteilen mehr Ressourcen beansprucht als das normale Zählen von Zeilen. Mit Vorsicht anwenden.
Frostschutz

Antworten:

14

Ein Problem dabei split --filterist, dass die Ausgabe verwechselt werden kann, sodass Sie eine halbe Zeile von Prozess 1 gefolgt von einer halben Zeile von Prozess 2 erhalten.

GNU Parallel garantiert, dass es keine Verwechslungen geben wird.

Angenommen, Sie möchten Folgendes tun:

 A | B | C

Aber dieses B ist furchtbar langsam, und deshalb möchten Sie das parallelisieren. Dann können Sie tun:

A | parallel --pipe B | C

GNU Parallel teilt sich standardmäßig auf \ n und eine Blockgröße von 1 MB. Dies kann mit --recend und --block eingestellt werden.

Weitere Informationen zu GNU Parallel finden Sie unter: http://www.gnu.org/s/parallel/

Sie können GNU Parallel in nur 10 Sekunden installieren mit:

wget -O - pi.dk/3 | sh 

Sehen Sie sich das Intro-Video auf http://www.youtube.com/playlist?list=PL284C9FF2488BC6D1 an

Ole Tange
quelle
1
Während ich bei der Installationsmethode stark anderer Meinung bin :-), +1, weil Ihre Lösung die meisten Probleme mit meiner löst.
LSerni
Dieser ist in der Tat nett. Haben Sie auch Vorschläge für die zu verwendenden Parameter? Ich weiß, dass Programm A mehr als 1 TB Daten ca. 5 GB pro Minute ausgibt. Das Programm B verarbeitet Daten 5-mal langsamer als A es ausgibt und ich habe 5 Kerne zur Verfügung für diese Aufgabe.
Jernej
GNU Parallel kann derzeit höchstens etwa 100 MB / s verarbeiten, daher werden Sie diese Grenze berühren. Das Optimum --block-sizehängt von der Größe des Arbeitsspeichers ab und davon, wie schnell Sie einen neuen starten können B. In deiner Situation würde ich nutzen --block 100Mund sehen, wie sich das verhält .
Ole Tange
@lserni Können Sie eine bessere Installationsmethode finden, die auf den meisten UNIX-Computern funktioniert und dem Benutzer einen ähnlichen Arbeitsaufwand abverlangt?
Ole Tange
4
Entschuldigung, ich habe mich nicht klar ausgedrückt. Die Installationsmethode, an die das Skript übergeben wurde, shist großartig. Das Problem liegt in der Weitergabe an sh: Herunterladen und Ausführen von ausführbarem Code von einer Site . Wohlgemerkt, vielleicht bin ich einfach zu paranoid, da man einwenden könnte, dass ein maßgeschneidertes RPM oder eine maßgeschneiderte DEB im Grunde dasselbe ist, und selbst das Posten des Codes auf einer Seite, die kopiert und eingefügt werden soll, würde dazu führen, dass die Leute dies blind tun wie auch immer.
LSerni
13

Beim Schreiben laufen A | Bbeide Prozesse bereits parallel. Wenn Sie davon ausgehen, dass sie nur einen Kern verwenden, liegt dies wahrscheinlich an den CPU-Affinitätseinstellungen (möglicherweise gibt es ein Tool, um einen Prozess mit unterschiedlicher Affinität zu erzeugen) oder daran, dass ein Prozess nicht ausreicht, um einen ganzen Kern und das System zu halten. " zieht es vor, "das Computing nicht zu verbreiten".

Um mehrere B's mit einem A zu betreiben, benötigen Sie ein Tool wie splitmit der --filterOption:

A | split [OPTIONS] --filter="B"

Dies kann jedoch die Zeilenreihenfolge in der Ausgabe durcheinander bringen, da die B-Jobs nicht alle mit der gleichen Geschwindigkeit ausgeführt werden. Wenn dies ein Problem ist, müssen Sie möglicherweise die B i-te Ausgabe in eine Zwischendatei umleiten und diese am Ende mit zusammenfügen cat. Dies kann wiederum einen beträchtlichen Speicherplatz erfordern.

Es gibt andere Optionen (z. B. können Sie jede Instanz von B auf eine einzelne zeilengepufferte Ausgabe beschränken, warten, bis eine ganze "Runde" von Bs beendet ist, das Äquivalent einer Reduction- to split- Map ausführen und catdie temporäre Ausgabe zusammen ausführen ). mit unterschiedlichen Wirkungsgraden. Die soeben beschriebene Option 'round' wartet darauf, dass die langsamste Instanz von B beendet wird. Sie hängt daher stark von der verfügbaren Pufferung für B ab. [m]bufferkann helfen oder auch nicht, je nachdem, welche Operationen durchgeführt werden.

Beispiele

Generieren Sie die ersten 1000 Zahlen und zählen Sie die Zeilen parallel:

seq 1 1000 | split -n r/10 -u --filter="wc -l"
100
100
100
100
100
100
100
100
100
100

Wenn wir die Zeilen "markieren" würden, würden wir sehen, dass jede erste Zeile an Prozess 1 gesendet wird, jede fünfte Zeile an Prozess 5 und so weiter. Darüber hinaus ist splitder erste Prozess in der Zeit, die zum Starten des zweiten Prozesses benötigt wird, bereits ein guter Weg, um sein Kontingent zu erhöhen:

seq 1 1000 | split -n r/10 -u --filter="sed -e 's/^/$RANDOM - /g'" | head -n 10
19190 - 1
19190 - 11
19190 - 21
19190 - 31
19190 - 41
19190 - 51
19190 - 61
19190 - 71
19190 - 81

Bei der Ausführung auf einem 2-Kern - Maschine seq, splitund die wcProzesse teilen sich die Kerne; Bei näherer Betrachtung belässt das System die ersten beiden Prozesse auf CPU0 und teilt CPU1 unter den Arbeitsprozessen auf:

%Cpu0  : 47.2 us, 13.7 sy,  0.0 ni, 38.1 id,  1.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 15.8 us, 82.9 sy,  0.0 ni,  1.0 id,  0.0 wa,  0.3 hi,  0.0 si,  0.0 st
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM     TIME+ COMMAND
 5314 lserni    20   0  4516  568  476 R 23.9  0.0   0:03.30 seq
 5315 lserni    20   0  4580  720  608 R 52.5  0.0   0:07.32 split
 5317 lserni    20   0  4520  576  484 S 13.3  0.0   0:01.86 wc
 5318 lserni    20   0  4520  572  484 S 14.0  0.0   0:01.88 wc
 5319 lserni    20   0  4520  576  484 S 13.6  0.0   0:01.88 wc
 5320 lserni    20   0  4520  576  484 S 13.3  0.0   0:01.85 wc
 5321 lserni    20   0  4520  572  484 S 13.3  0.0   0:01.84 wc
 5322 lserni    20   0  4520  576  484 S 13.3  0.0   0:01.86 wc
 5323 lserni    20   0  4520  576  484 S 13.3  0.0   0:01.86 wc
 5324 lserni    20   0  4520  576  484 S 13.3  0.0   0:01.87 wc

Beachten Sie, dass besonders splitviel CPU verbraucht wird. Dies wird proportional zu den Bedürfnissen von A abnehmen. dh wenn A ein schwererer Prozess ist als seq, splitverringert sich der relative Overhead von . Aber wenn A ein sehr leichter Prozess ist und B ziemlich schnell ist (so dass Sie nicht mehr als 2-3 Bs benötigen, um mit A mitzuhalten), lohnt es sich möglicherweise nicht, mit split(oder allgemein mit Pipes) zu parallelisieren .

LSerni
quelle
Interessant, dass die auf Ubuntu gefundene Aufteilung nicht die Option --filter enthält. Welche Art von Betriebssystem verwenden Sie dafür?
Jernej
Linux OpenSuSE 12.3 mit coreutils ( gnu.org/software/coreutils/manual/html_node/… ). Ich werde versuchen, ein Ubuntu zu besorgen. Möglicherweise haben sie den Namen geändert, um ein gleichnamiges Tool aufzunehmen.
LSerni
Sind Sie sicher, dass die split --filterOption fehlt? Auf meinem Ubuntu 12.04-LTS ("wheezy / sid") ist es da und meine Beispiele funktionieren. Könnten Sie eine andere splitals die in GNU coreutils installiert haben ?
LSerni
Danke dafür. Ich musste eine neuere Version von Coreutils installieren. Übrigens ist mir aufgefallen, dass wenn ich Programm A alleine starte, es einen ganzen Kern (100%) frisst, wenn ich A | starte B dann essen sie zusammen einen ganzen Kern, verarbeiten A zu 15% und verarbeiten B zu 85%. Verstehst du zufällig, warum das so ist?
Jernej
2
Dies liegt wahrscheinlich an der Blockierung . Wenn B schwerer als A ist, kann A seine Ausgabe nicht senden und wird verlangsamt. Eine andere Möglichkeit ist, dass A während des Betriebs B nachgibt (z. B. Festplatte / Netz). Auf einem anderen System kann es vorkommen, dass B 100% von CPU1 verschlingt und A 18% von CPU0 zugewiesen bekommt. Sie benötigen wahrscheinlich 85/15 ~ 5.67 = zwischen 5 und 6 Instanzen von B, um eine einzelne A-Instanz zur Sättigung eines einzelnen Kerns zu erhalten. E / A, falls vorhanden, können diese Werte jedoch verzerren.
LSerni