Warum ist die Kommunikation über Shared Memory so viel langsamer als über Warteschlangen?

8

Ich verwende Python 2.7.5 auf einem aktuellen Apple MacBook Pro, das vier Hardware- und acht logische CPUs hat; Das Dienstprogramm sysctl gibt Folgendes aus:

%Vor%

Ich muss eine ziemlich komplizierte Verarbeitung in einer großen 1-D-Liste oder einem Array durchführen und dann das Ergebnis als eine Zwischenausgabe speichern, die zu einem späteren Zeitpunkt in einer nachfolgenden Berechnung innerhalb meiner Anwendung wieder verwendet wird. Die Struktur meines Problems eignet sich sehr gut zur Parallelisierung, also dachte ich, ich würde versuchen, Pythons Multiprozessor-Modul zu verwenden, um das 1D-Array in mehrere Teile zu unterteilen (entweder 4 oder 8 Stück, ich bin mir noch nicht sicher welche) die Berechnungen parallel und anschließend die resultierende Ausgabe in ihr endgültiges Format wieder zusammen. Ich versuche zu entscheiden, ob multiprocessing.Queue() (Nachrichtenwarteschlangen) oder multiprocessing.Array() (gemeinsam genutzter Speicher) als mein bevorzugter Mechanismus für die Übertragung der resultierenden Berechnungen von den Kindprozessen an den Hauptmutterprozess verwendet werden soll, mit denen ich experimentiert habe ein paar "Spielzeug" -Modelle, um sicherzustellen, dass ich verstehe, wie das Multiprocessing-Modul tatsächlich funktioniert. Ich bin jedoch auf ein eher unerwartetes Ergebnis gestoßen: Bei der Erstellung von zwei im Wesentlichen äquivalenten Lösungen für das gleiche Problem scheint die Version, die Shared Memory für die Interprozesskommunikation verwendet, wesentlich mehr Ausführungszeit (30x mehr!) Zu benötigen als die Version mit Message Warteschlangen. Im Folgenden habe ich zwei verschiedene Versionen von Beispielquellcode für ein "Spielzeug" -Problem aufgeführt, das eine lange Folge von Zufallszahlen unter Verwendung paralleler Prozesse erzeugt und das agglomerierte Ergebnis auf zwei verschiedene Arten an einen übergeordneten Prozess zurückgibt: zuerst mithilfe von Nachrichtenwarteschlangen und das zweite Mal Shared Memory verwenden.

Hier ist die Version, die Nachrichtenwarteschlangen verwendet:

%Vor%

Wenn ich es ausführe, erhalte ich ein Ergebnis, das normalerweise so aussieht:

%Vor%

Nun, hier ist das äquivalente Code-Segment, aber refactored nur leicht, so dass es Shared Memory statt Queues verwendet:

%Vor%

Wenn ich die Shared Memory-Version ausführe, sieht das typische Ergebnis jedoch eher so aus:

%Vor%

Meine Frage: Warum gibt es einen so großen Unterschied in den Ausführungsgeschwindigkeiten (ungefähr 0,5 Sekunden vs. 15 Sekunden, ein Faktor 30!) zwischen den beiden Versionen meines Codes? Und vor allem, wie kann ich die Shared-Memory-Version ändern, damit sie schneller läuft?

    
stachyra 12.08.2014, 18:39
quelle

1 Antwort

13

Dies liegt daran, dass multiprocessing.Array standardmäßig eine Sperre verwendet, um zu verhindern, dass mehrere Prozesse gleichzeitig darauf zugreifen:

  

multiprocessing.Array (typecode_or_type, size_or_initializer, *,   Sperre = True)

     

...

     

Wenn die Sperre auf "True" (die Standardeinstellung) gesetzt ist, wird ein neues Sperrobjekt erstellt, um den Zugriff auf den Wert zu synchronisieren. Wenn die Sperre ein Lock- oder RLock-Objekt ist   Dann wird der Synchronisierungszugriff auf den Wert verwendet. Wenn das Schloss ist   False, dann wird der Zugriff auf das zurückgegebene Objekt nicht automatisch erfolgen   geschützt durch ein Schloss, so dass es nicht unbedingt "prozesssicher" ist.

Das bedeutet, dass Sie nicht gleichzeitig in das Array schreiben - nur ein Prozess kann gleichzeitig darauf zugreifen. Da Ihre Beispiel-Worker fast nichts anderes tun als Array-Schreibzugriffe, stört das ständige Warten auf diese Sperre die Leistung. Wenn Sie lock=False verwenden, wenn Sie das Array erstellen, ist die Leistung viel besser:

Mit lock=True :

%Vor%

Mit lock=False :

%Vor%

Beachten Sie, dass die Verwendung von lock=False bedeutet, dass Sie den Zugriff auf Array manuell schützen müssen, wenn Sie etwas tun, das nicht prozesssicher ist. Ihr Beispiel besteht darin, Prozesse in eindeutige Teile schreiben zu lassen, also ist es in Ordnung. Wenn Sie jedoch dabei versuchen würden, davon zu lesen oder verschiedene Prozesse in überlappende Teile schreiben würden, müssten Sie eine Sperre manuell erfassen.

    
dano 12.08.2014, 18:44
quelle