Mit Joblib Python-Programme parallelisieren und memorisieren

Galina Peshkova, 123RF

Eine Bibliothek für viele Jobs

Parallelisierung, Memorization sowie Speichern und Laden von Objekten: Die Python-Bibliothek Joblib erledigt häufige Problemstellungen im Handumdrehen und lässt Programmierer damit sofort zum Kern ihrer Arbeit vordringen.
Drahtlose Netzwerke sind überall: Zu Hause, im Café und in der Firma. Im Gegensatz zu Kabelnetzen verliert der Admin bei WLANs allerdings schnell die ... (mehr)

In den letzten Jahren bereichern neue Programmierkonzepte die Computerwelt. Statt der Geschwindigkeit der Prozessoren nimmt in vielen Rechenzentren vielmehr deren Anzahl zu. Die parallele Verarbeitung erlaubt den Umgang mit großen Datenmengen, erfordert aber auch einen oft heiklen Übergang von traditionellen, sequenziellen Vorgehensweisen zu eigens angepassten Methoden. Die Python-Bibliothek Joblib erspart bei typischen Vorgehensweisen wie Caching und Parallelisierung viel fehlerträchtige Programmierarbeit.

Manche aufwendige Aufgaben drängen sich förmlich auf für eine parallelisierte Verarbeitung. Große Datensätze, in denen jeder Eintrag voneinander unabhängig steht, lassen sich hervorragend von vielen Prozessoren gleichzeitig verarbeiten (siehe Abbildung 1 ). Solche für die Parallelisierung prädestinierten Aufgaben heißen auf Englisch embarrassingly parallel, zu Deutsch etwa "peinlich parallel". Wo genau der Ausdruck herkommt, ist unklar, aber er deutet an, dass die Umwandlung eines solchen Algorithmus in eine parallelisierte Version nicht lange dauern sollte.

Abbildung 1: Probleme, bei denen sich Eingabeobjekte unabhängig voneinander nebenläufig verarbeiten lassen, heißen embarassingly parallel.

Erfahrene Entwickler wissen andererseits, dass in der alltäglichen Programmierpraxis bei jeder Neuimplementierung mehr oder weniger große Probleme auftreten und dass man sich schnell in implementatorischen Details verzettelt. Für die umstandslose Lösung von Embarassingly-parallel-Aufgaben stellt das Modul Joblib deshalb die Klasse »Parallel« bereit. Es setzt eine beliebige Funktion voraus, die genau ein Argument entgegennimmt.

Parallele Dekoration

Für die Zusammenarbeit zwischen »Parallel« und der besagten Funktion, beispielsweise »f(x)« , liefert Joblib die Methode »delayed()« mit, die als Decorator dient. Listing 1 zeigt ein einfaches Beispiel mit einer Beispielimplementation von »f(x)« , die lediglich »x« unverändert zurückgibt. Die in Listing 1 gezeigte »for« -Schleife iteriert über eine Liste »l« und übergibt die einzelnen Werte an »f(x)« , jedes Listenelement aus »l« resultiert in einem eigenen Job.

Listing 1

Joblib: embarassingly parallel

 

Den interessantesten Teil erledigt dabei das ad hoc generierte anonyme »Parallel« -Objekt. Es verteilt die Aufrufe von »f(x)« auf die verschiedenen CPUs oder Prozessorkerne im Rechner. Wie viele es nutzt, bestimmt das Argument »n_jobs« . Standardmäßig steht es auf 1, sodass Parallel nur einen Unterprozess startet. Setzt man es auf -1, verwendet es alle vorhandenen Prozessorkerne, bei -2 lässt es einen unbenutzt, bei -3 einen weiteren und so weiter. Alternativ nimmt »n_jobs« positive Ganzzahlen entgegen, die direkt die Anzahl zu verwendender Prozesse definiert.

Der Wert von »n_jobs« darf auch über der Anzahl physisch verfügbarer Prozessorkerne liegen; die Parallel-Klasse startet einfach die per »n_jobs« definierte Anzahl von Python-Prozessen und das Betriebssystem lässt sie nebeneinander laufen. Dies bedeutet übrigens auch, dass der Austausch globaler Variablen zwischen den einzelnen Jobs unmöglich ist, denn verschiedene Betriebssystemprozesse können nicht direkt untereinander kommunizieren. Parallel umgeht diese Einschränkung, indem es die nötigen Objekte serialisiert und zwischenspeichert.

Die optimale Anzahl von Prozessen hängt vor allem von der Art der zu bewältigenden Aufgaben ab. Liegt ihr Flaschenhals weniger in der Prozessorleistung als im Lesen und Schreiben von Daten auf die lokale Festplatte oder übers Netzwerk, darf die Zahl der Prozesse höher liegen; als Faustregel dient hier häufig etwa die Anzahl der vorhandenen Prozessorkerne mal 1,5. Lastet jeder Prozess hingegen eine CPU dauerhaft voll aus, sollte sie nicht über der Zahl physisch vorhandener Prozessoren liegen.

Wie läuft's?

Des Weiteren bietet die Parallel-Klasse mithilfe des optionalen »verbose« -Arguments die regelmäßige Ausgabe von Statusmeldungen an, die den Gesamtfortschritt veranschaulichen. Sie zeigen die Anzahl abgearbeiteter und verbleibender Jobs sowie falls möglich die geschätzte Rest- und die bereits verstrichene Zeit. »verbose« steht per Vorgabe auf 0; setzt man eine beliebige positive Zahl ein, erhöht man die Ausgabefrequenz. Dabei gilt: je höher der Wert von »verbose« , desto mehr Zwischenstufen gibt Joblib aus. Listing 2 zeigt eine typische Ausgabe.

Listing 2

Parallel mit Statusberichten

 

Die genaue Anzahl der Zwischenberichte schwankt, weil zu Beginn der Ausführung häufig noch unklar ist, wie viele Jobs insgesamt anstehen, es handelt sich also nur um einen Näherungswert. Setzt man »verbose« auf einen Wert über 10, gibt Parallel nach jeder einzelnen Iteration den aktuellen Status aus. Außerdem bietet das Argument die Möglichkeit, die Ausgabe umzuleiten: Steht »verbose« auf einem Wert von über 50, schreibt Parallel die Statusberichte auf die Standardausgabe, liegt er darunter, verwendet er »stderr« , also den Fehlerkanal der ausführenden Shell.

Als drittes, ebenfalls optionales Argument nimmt Parallel »pre_dispatch« an. Es definiert, wie viele der Jobs die Klasse sofort zur Verarbeitung einreiht. Standardmäßig lädt es direkt alle Listenelemente in den Speicher, »pre_dispatch« steht auf »'all'« . Beansprucht die Verarbeitung allerdings viel Arbeitsspeicher, bietet ein geringerer Wert eine Gelegenheit, RAM zu sparen. Dazu übergibt man hier einen positiven Integer-Wert.

comments powered by Disqus
Einmal pro Woche aktuelle News, kostenlose Artikel und nützliche ADMIN-Tipps.
Ich habe die Datenschutzerklärung gelesen und bin einverstanden.

Konfigurationsmanagement

Ich konfiguriere meine Server

  • von Hand
  • mit eigenen Skripts
  • mit Puppet
  • mit Ansible
  • mit Saltstack
  • mit Chef
  • mit CFengine
  • mit dem Nix-System
  • mit Containern
  • mit anderer Konfigurationsmanagement-Software

Ausgabe /2023