Howto: GNU parallel

05 Apr 2012

Quelle: GNU CC BY-ND 3.0

In letzter Zeit habe ich mal intensiver mit GNU parallel herum gespielt. Bei GNU parallel handelt es sich um ein kleines Tool für Linux- und Unix-Umgebungen, welches ihm gestellte Aufgaben parallel abarbeiten und auch auf verschiedene Rechner verteilen kann. Damit kann man meist Zeit sparen und den Rechner besser auslasten.

Doch zuerst sollten wir das kleine Tool ersteinmal installieren. Dies geschieht durch herunterladen der aktuellen Version und dem Bauen der Software mit dem bekannten Dreizeiler ./configure, make, sudo make install. Ich verwendete für meine Tests Ubuntu 11.10 als auch Debian 6.

wget http://ftp.gnu.org/gnu/parallel/parallel-20120222.tar.bz2
tar xf parallel-20120222.tar.bz2
cd parallel-20120222
./configure
make
sudo make install

Die Version von GNU parallel im Paket moreutils auf meinem Debian war ziemlich alt und funktionierte nicht wie erwartet. Deshalb sollte man lieber die neuste Version selber bauen, es dauert nur wenige Sekunden.

Hat man das kleine Tool erstmal auf seiner heimischen Platte eingerichtet kann man mal erste Tests durchführen, um ein Gefühl für die Parameter zu bekommen. So habe ich erstmal ein paar MD5-Summen von zwei größeren Testdateien erzeugt. Zuerst in der gewohnten Art und anschließend mit GNU parallel.

time md5sum *.iso
676d2996cd4838a8762a8ac5c210a158 test1.iso
676d2996cd4838a8762a8ac5c210a158 test2.iso

real 1m18.117s
user 0m13.953s
sys 0m2.288s

time parallel md5sum {} ::: *.iso
676d2996cd4838a8762a8ac5c210a158 test1.iso
676d2996cd4838a8762a8ac5c210a158 test2.iso

real 0m40.886s
user 0m11.205s
sys 0m1.772s

Man sieht sofort, dass die parallele Ausführung das System besser auslastet und dadurch schneller ist. Der parallel-Aufruf hier besagt, dass der Befehl md5sum auf alle Dateien angewendet werden soll, welche über den Standard-Input angegeben werden. Die Klammern {} werden durch den kompletten Dateinamen inklusive Pfad ersetzt. Andere Kombinationen wie {.}, {/} oder {/.} werden durch den Dateinamen ohne Dateiendung, Dateiname ohne Pfad oder Dateiname ohne Pfad und Dateiendung ersetzt. Alles was nach den Doppelpunkten ::: kommt, wird als Standardeingabe angesehen.

Aber auch bei alltäglichen Aufgaben kann das Tool hilfreich sein. So sind einige Kombinationen nicht aufgrund der parallelen Abarbeitung schneller, sondern weil man sie geschickt kombinieren kann. So bedeutet der Parameter -X, dass die per Standardeingabe übergebenen Werte soweit möglich gesammelt und erst dann dem entsprechenden Programm übergeben werden soll. Hier ein kleines Beispiel zur Verdeutlichung.

time find /usr/share/man/man* -name '*.gz' -exec cp {} . \;

real 0m23.109s
user 0m27.102s
sys 0m7.264s

time find /usr/share/man/man* -name '*.gz' | parallel -X "cp {} ."

real 0m6.900s
user 0m0.732s
sys 0m0.992s

Beide Aufrufe suchen nach gepackten Manpages und kopieren sie in das aktuelle Verzeichnis. Der erste Befehl allerdings führt den Kopierbefehl für jede gefundene Datei aus. Der zweite wartet erst auf das Ergebnis der Suche und kopiert die gefundenen Dateien in einem Rutsch in das aktuelle Verzeichnis. Das führt sichbar zu einer dramatischen Geschwindigkeitssteigerung.

Bei weniger rechenintensiven Aufgaben, welche hauptsächlich die Festplatte beanspruchen bringt die Paralleliserung meistens keinen großen Vorteil. In besonderen Fällen kann sich die Parallelisierung sogar negativ auf die Dauer der Abarbeitung auswirken, da die Fesplatte dann durch die vielen verteilten Lese- und Schreibzugriffe zum Flaschenhals wird.

Das folgende Beispiel zeigt, dass die parallelisierte Umwandlung der gerade kopierten Manpages nur wenig bringt aber der Aufruf sich eventuell viel einfacher schreiben lässt. So kann man die folgende For-Schleife

mkdir html
time for man in `ls *.gz`;
do zcat $man | man2html > html/`basename $man .gz`.html;
done

real 0m43.697s
user 0m57.504s
sys 0m28.422s

elegant und einfach als folgenden parallel-Aufruf schreiben.

time parallel "zcat {} | man2html > html/{/.}.html" ::: *.gz

real 0m31.906s
user 1m5.136s
sys 0m25.538s

Aber neben der simplen Parallelisierung auf einem Host, der sich an der Anzahl der Prozessorkerne orientiert, d.h. pro Kern ein paralleler Prozess, änderbar mit dem Parameter -j, kann GNU parallel seine Aufgaben auch auf mehrere Rechner verteilen. Voraussetzung ist hierbei eine ssh-Verbindung zwischen den Rechnern auf Zertifikatsbasis, damit GNU parallel sich ohne Passwort mit den Rechnern verbinden kann. Außerdem muss natürlich auf allen Rechnern GNU parallel installiert sein.
Prinzipiell wäre dies schon ausreichend. Meine praktischen Tests zeigten allerdings, dass es günstig ist einen gleichnamigen Nutzeraccount für GNU parallel einzurichten und die Daten per NFS auf allen Rechnern verfügbar zu machen. So umgeht man Probleme beim Hin- und Rücktransport der Daten per ssh, vom Geschindigeitsgewinn mal ganz abgesehen, sondern auch die Pfadangaben sind so auf allen Rechnern gleich, was zu weniger Fehlern und Frust führt.
Ich habe also einen NFS-Share namens data in jedem Home-Verzeichnis des Nutzers parallel auf beiden Rechnern gemountet, SSH-Verbindungen auf Zertifikatsbasis eingerichtet und GNU parallel wie oben beschrieben installiert.

Ein erster Test auf einem Host zur Gewinnung eines Vergleichswerts:

parallel@node1:~/data$ time bzip2 -9 /home/parallel/data/*.iso

real 22m32.795s
user 22m16.108s
sys 0m12.933s

parallel@node1:~/data$ time bunzip2 /home/parallel/data/*.bz2

real 10m8.291s
user 9m51.253s
sys 0m13.713s

Das gleiche mit GNU parallel:

parallel@node1:~/data$ time parallel 'bzip2 -9 {}' ::: /home/parallel/data/*.iso

real 15m35.153s
user 23m10.279s
sys 0m14.765s

parallel@node1:~/data$ time parallel 'bunzip2 {}' ::: /home/parallel/data/*.bz2

real 7m18.560s
user 10m17.035s
sys 0m17.689s

Ein weiterer Test mit zwei Hosts. Der zweite Hosts kann per Parameter -S angegeben werden. Der Doppelpunkt steht für den eigenen Host.

parallel@node1:~/data$ time parallel -S :,node2 'bzip2 -9 {}' ::: /home/parallel/data/*.iso

real 9m26.619s
user 16m2.248s
sys 0m15.701s

parallel@node1:~/data$ time parallel -S :,node2 'bunzip2 {}' ::: /home/parallel/data/*.bz2

real 4m43.201s
user 7m10.763s
sys 0m15.429s

Der Geschwindigkeitsvorteil wird sicherlich deutlich. Sowohl auf einem Host und noch deutlicher bei der Verwendung eines weiteren Hosts.

Zum Abschluss ein kleiner Appetithappen. Man kann GNU parallel auch als einfachen Job-Manager verwenden. Dazu legt man eine leere Datei an und weist GNU parallel an, alles zu verarbeiten was da noch kommen mag. Alles was es dann verarbeiten soll fügt man dieser Datei an. Und schon ist das Batch-System fertig:

parallel@node1:~$ touch data/jobs
parallel@node1:~$ tail -f data/jobs | parallel -S :,node2

parallel@node2:~$ echo "bzip2 -9 data/test1.iso" >> data/jobs
parallel@node2:~$ echo "bzip2 -9 data/test2.iso" >> data/jobs
parallel@node2:~$ echo "bzip2 -9 data/test3.iso" >> data/jobs

Funktioniert super. Für weitere Informationen zu GNU parallel kann ich die Man-Page und dieses PDF empfehlen.

Wenn man sich erstmal in das Tool eingefunden hat und erste erfolgreiche Gehversuche hinter sich hat möchte man dieses Tool nie mehr missen. Ich hoffe es findet auch bei euch seinen Platz in der digitalen Werkzeugkiste.

batch gnu gnu-parallel howto linux shell unix
comments powered by Disqus