Effizientes Multiprocessing der massiven, brachialen Kraftmaximierung in Python 3

8

Dies ist eine Erweiterung meiner letzten Frage Vermeiden von Rennbedingungen in Python 3's Multiprocessing Queues . Hoffentlich ist diese Version der Frage spezifischer.

TL; DR: In einem Multiprozessormodell, in dem Arbeitsprozesse aus einer Warteschlange mit multiprocessing.Queue gespeist werden, warum sind meine Arbeitsprozesse so inaktiv? Jeder Prozess hat seine eigene Eingabewarteschlange nicht für eine gemeinsame Warteschlange Sperre kämpfen, aber die Warteschlangen verbringen viel Zeit eigentlich nur leer. Der Hauptprozess führt einen E / A-gebundenen Thread aus - verlangsamt dies das CPU-gebundene Füllen der Eingabewarteschlangen?

Ich versuche, das maximale Element des kartesischen Produkts von N Mengen jeweils mit M_i Elementen (für 0 & lt; = i & lt; N) unter einer bestimmten Bedingung zu finden. Es sei daran erinnert, dass die Elemente des kartesischen Produkts Länge-N-Tupel sind, deren Elemente Elemente der N Mengen sind. Ich nenne diese Tupel "Kombinationen", um die Tatsache hervorzuheben, dass ich jede Kombination der Originalsätze durchlaufe. Eine Kombination erfüllt die Einschränkung, wenn meine Funktion is_feasible True zurückgibt. In meinem Problem versuche ich die Kombination zu finden, deren Elemente das größte Gewicht haben: sum(element.weight for element in combination) .

Meine Problemgröße ist groß, aber auch der Server meiner Firma. Ich versuche, den folgenden seriellen Algorithmus als parallelen Algorithmus neu zu schreiben.

%Vor%

Mein aktueller Multiprocessing-Ansatz besteht darin, Arbeitsprozesse zu erstellen und sie mit einer Eingabewarteschlange zu füttern. Wenn die Arbeiter eine Giftpille erhalten, stellen sie die beste Kombination zusammen sie haben in einer Ausgabewarteschlange gesehen und beenden. Ich fülle die Eingabewarteschlange aus dem Hauptthread des Hauptprozesses. Ein Vorteil dieser Technik ist, dass ich einen sekundären Thread aus dem Hauptprozess erzeugen kann, um ein Überwachungstool auszuführen (nur eine REPL, mit der ich sehen kann, wie viele Kombinationen bisher verarbeitet wurden und wie voll die Warteschlangen sind).

> %Vor%

Ich hatte ursprünglich alle Arbeiter aus einer Eingangswarteschlange lesen, aber festgestellt, dass keiner von ihnen die CPU traf. Als sie feststellten, dass sie ihre ganze Zeit damit verbrachten, auf queue.get () zu warten, um sie zu entsperren, gab ich ihnen ihre eigenen Warteschlangen. Das erhöhte den Druck auf die CPU, daher dachte ich, dass die Arbeiter häufiger aktiv waren. Die Warteschlangen verbringen jedoch die meiste Zeit leer! (Ich weiß das von der Überwachungs-REPL, die ich erwähnt habe). Dies deutet darauf hin, dass die Hauptschleife, die die Warteschlangen füllt, langsam ist. Hier ist diese Schleife:

%Vor%

Ich vermute, der Engpass ist worker.in_q.put() . Wie mache ich das schneller? Mein erster Instinkt war, die Arbeiter langsamer zu machen, aber das macht einfach keinen Sinn ... Ist das Problem, dass der Monitor-Thread die Schleife zu oft stoppt? Wie könnte ich das sagen?

Alternativ gibt es eine andere Möglichkeit, dies zu implementieren, die nicht so viel auf Sperren wartet?

    
wkschwartz 19.05.2012, 23:25
quelle

1 Antwort

4

Wie sehen Ihre Elemente aus? Es könnte sein, dass das Beizen, um sie in die Warteschlange zu stellen, langsam ist, was natürlich ein Flaschenhals wäre. Beachten Sie, dass jedes Element unabhängig und immer und immer wieder gebeizt wird.

Wenn dies der Fall ist, könnte dieser Ansatz helfen:

  • Wählen Sie einen Satz mit Kardinalität & gt; = Ihre Anzahl an Arbeitern. Idealerweise wäre es viel mehr als die Anzahl der Arbeiter. Nennen Sie diese Menge A und weisen Sie jedem Arbeiter ungefähr gleiche Teilmengen von A zu. Übermitteln Sie diese Teilmenge an jeden Mitarbeiter.
  • Verteilen Sie den vollständigen Inhalt aller Mengen außer A an jeden der Worker (wahrscheinlich einmal durch pickle.dumps und dann die gleiche Zeichenfolge an jeden Worker oder möglicherweise über den gemeinsamen Speicher oder was auch immer).
  • Dann hat jeder Worker die vollständigen Informationen, die er für seine Teilmenge benötigt. Es kann auf seinem fröhlichen Weg über product(my_A_subset, *other_sets) (möglicherweise anders bestellt) beginnen, wobei zwischen jedem Job (oder alle drei Jobs oder was auch immer) eine Art Stoppsignal abgefragt wird. Dies muss nicht über eine Warteschlange erfolgen, ein Ein-Bit-Shared-Memory-Wert funktioniert einwandfrei.
Dougal 20.05.2012, 01:19
quelle