Angenommen, ich habe ein großes Speicher-Numpy-Array. Ich habe eine Funktion func
, die dieses riesige Array als Eingabe verwendet (zusammen mit einigen anderen Parametern). func
mit verschiedenen Parametern kann parallel ausgeführt werden. Beispielsweise:
def func(arr, param):
# do stuff to arr, param
# build array arr
pool = Pool(processes = 6)
results = [pool.apply_async(func, [arr, param]) for param in all_params]
output = [res.get() for res in results]
Wenn ich eine Multiprozessor-Bibliothek verwende, wird dieses riesige Array mehrmals in verschiedene Prozesse kopiert.
Gibt es eine Möglichkeit, verschiedene Prozesse dasselbe Array gemeinsam nutzen zu lassen? Dieses Array-Objekt ist schreibgeschützt und wird niemals geändert.
Was ist komplizierter, wenn arr kein Array, sondern ein beliebiges Python-Objekt ist, gibt es eine Möglichkeit, es zu teilen?
[BEARBEITET]
Ich habe die Antwort gelesen, bin aber immer noch etwas verwirrt. Da fork () Copy-on-Write ist, sollten wir keine zusätzlichen Kosten verursachen, wenn wir neue Prozesse in der Python-Multiprocessing-Bibliothek erzeugen. Der folgende Code deutet jedoch auf einen enormen Overhead hin:
from multiprocessing import Pool, Manager
import numpy as np;
import time
def f(arr):
return len(arr)
t = time.time()
arr = np.arange(10000000)
print "construct array = ", time.time() - t;
pool = Pool(processes = 6)
t = time.time()
res = pool.apply_async(f, [arr,])
res.get()
print "multiprocessing overhead = ", time.time() - t;
Ausgabe (und im Übrigen steigen die Kosten mit zunehmender Größe des Arrays, sodass ich vermute, dass beim Kopieren des Speichers immer noch Overhead besteht):
construct array = 0.0178790092468
multiprocessing overhead = 0.252444982529
Warum ist der Aufwand so groß, wenn wir das Array nicht kopiert haben? Und welchen Teil rettet mich der gemeinsame Speicher?
Antworten:
Wenn Sie ein Betriebssystem verwenden, das Copy-on-Write-
fork()
Semantik verwendet (wie jedes gängige Unix), steht es allen untergeordneten Prozessen zur Verfügung, solange Sie Ihre Datenstruktur nicht ändern, ohne zusätzlichen Speicherplatz zu beanspruchen. Sie müssen nichts Besonderes tun (außer unbedingt sicherstellen, dass Sie das Objekt nicht verändern).Das effizienteste, was Sie für Ihr Problem tun können, ist, Ihr Array in eine effiziente Array-Struktur (mit
numpy
oderarray
) zu packen , diese in den gemeinsam genutzten Speicher zu legen, sie zu verpackenmultiprocessing.Array
und an Ihre Funktionen zu übergeben. Diese Antwort zeigt, wie das geht .Wenn Sie einen wollen beschreibbaren gemeinsam genutztes Objekt, dann müssen Sie es mit irgendeiner Art von Synchronisation oder Verriegelung wickeln.
multiprocessing
Hierfür stehen zwei Methoden zur Verfügung : eine mit gemeinsam genutztem Speicher (geeignet für einfache Werte, Arrays oder c-Typen) oder einManager
Proxy, bei dem ein Prozess den Speicher enthält und ein Manager den Zugriff von anderen Prozessen (auch über ein Netzwerk) auf ihn schaltet .Der
Manager
Ansatz kann mit beliebigen Python-Objekten verwendet werden, ist jedoch bei Verwendung des gemeinsam genutzten Speichers langsamer als der entsprechende Ansatz, da die Objekte serialisiert / deserialisiert und zwischen Prozessen gesendet werden müssen.Es gibt eine Fülle von parallelen Verarbeitungsbibliotheken und Ansätze in Python zur Verfügung .
multiprocessing
ist eine ausgezeichnete und abgerundete Bibliothek, aber wenn Sie spezielle Bedürfnisse haben, ist vielleicht einer der anderen Ansätze besser.quelle
apply_async
sollte auf das gemeinsam genutzte Objekt im Gültigkeitsbereich direkt anstatt über seine Argumente verweisen.Ich bin auf dasselbe Problem gestoßen und habe eine kleine Dienstprogrammklasse für gemeinsam genutzten Speicher geschrieben, um das Problem zu umgehen.
Ich verwende
multiprocessing.RawArray
(lockfree) und auch der Zugriff auf die Arrays ist überhaupt nicht synchronisiert (lockfree). Achten Sie darauf, dass Sie nicht auf Ihre eigenen Füße schießen.Mit der Lösung bekomme ich auf einem Quad-Core i7 eine Beschleunigung um den Faktor ca. 3.
Hier ist der Code: Fühlen Sie sich frei, ihn zu verwenden und zu verbessern, und melden Sie alle Fehler zurück.
quelle
Dies ist der beabsichtigte Anwendungsfall für Ray , eine Bibliothek für paralleles und verteiltes Python. Unter der Haube werden Objekte mithilfe des Apache Arrow -Datenlayouts (im Nullkopieformat) serialisiert und in einem Objektspeicher mit gemeinsamem Speicher gespeichert, sodass mehrere Prozesse auf sie zugreifen können, ohne Kopien zu erstellen.
Der Code würde wie folgt aussehen.
Wenn Sie nicht aufrufen,
ray.put
wird das Array weiterhin im gemeinsam genutzten Speicher gespeichert. Dies erfolgt jedoch einmal pro Aufruf vonfunc
, was nicht das ist, was Sie möchten.Beachten Sie, dass dies nicht nur für Arrays funktioniert, sondern auch für Objekte, die Arrays enthalten , z. B. Wörterbücher, die Ints wie folgt Arrays zuordnen.
Sie können die Leistung der Serialisierung in Ray mit der von Pickle vergleichen, indem Sie in IPython Folgendes ausführen.
Die Serialisierung mit Ray ist nur geringfügig schneller als mit Pickle, aber die Deserialisierung ist aufgrund der Verwendung des gemeinsam genutzten Speichers 1000-mal schneller (diese Anzahl hängt natürlich vom Objekt ab).
Siehe die Ray-Dokumentation . Weitere Informationen zur schnellen Serialisierung mit Ray und Arrow finden Sie hier . Hinweis: Ich bin einer der Ray-Entwickler.
quelle
Wie Robert Nishihara bereits erwähnt hat, macht Apache Arrow dies einfach, insbesondere mit dem Plasma-In-Memory-Objektspeicher, auf dem Ray basiert.
Ich habe Gehirn-Plasma speziell aus diesem Grunde - schnellen Laden und in einer Flasche App von großen Objekten neu zu laden. Es ist ein Objekt-Namespace mit gemeinsamem Speicher für Apache Arrow-serialisierbare Objekte, einschließlich
pickle
'd Bytestrings, die von generiert wurdenpickle.dumps(...)
.Der Hauptunterschied zu Apache Ray und Plasma besteht darin, dass die Objekt-IDs für Sie erfasst werden. Alle Prozesse, Threads oder Programme, die lokal ausgeführt werden, können die Werte der Variablen gemeinsam nutzen, indem sie den Namen eines beliebigen
Brain
Objekts aufrufen .quelle