2013-01-02 10 views
41

Ich habe drei große Listen. Zuerst enthält Bitarrays (Modul Bitarray 0.8.0) und die anderen beiden enthalten Arrays von ganzen Zahlen.Shared Memory im Multiprocessing

l1=[bitarray 1, bitarray 2, ... ,bitarray n] 
l2=[array 1, array 2, ... , array n] 
l3=[array 1, array 2, ... , array n] 

Diese Datenstrukturen benötigen ziemlich viel RAM (~ 16 GB insgesamt).

Wenn ich 12 Teilprozesse beginnen:

multiprocessing.Process(target=someFunction, args=(l1,l2,l3)) 

Bedeutet dies, dass l1, l2 und l3 wird für jeden Teilprozess kopiert werden oder werden die Teilprozesse teilen diese Listen? Oder um direkter zu sein, werde ich 16 GB oder 192 GB RAM verwenden?

someFunction liest einige Werte aus diesen Listen und führt dann basierend auf den gelesenen Werten einige Berechnungen durch. Die Ergebnisse werden an den übergeordneten Prozess zurückgegeben. Die Listen l1, l2 und l3 werden nicht von someFunction geändert.

Daher würde ich annehmen, dass die Unterprozesse diese riesigen Listen nicht benötigen und nicht kopieren würden, sondern sie nur mit dem Elternteil teilen würden. Bedeutet das, dass das Programm aufgrund des Kopier-auf-Schreib-Ansatzes unter Linux 16 GB RAM benötigt (unabhängig davon, wie viele Unterprozesse ich starte)? Bin ich richtig oder fehle ich etwas, was dazu führen würde, dass die Listen kopiert werden?

BEARBEITEN: Ich bin immer noch verwirrt, nachdem ich ein bisschen mehr über das Thema gelesen habe. Auf der einen Seite benutzt Linux Copy-on-Write, was bedeutet, dass keine Daten kopiert werden. Auf der anderen Seite ändert der Zugriff auf das Objekt seine ref-count (ich bin immer noch unsicher, warum und was das bedeutet). Aber wird das gesamte Objekt kopiert?

Zum Beispiel, wenn ich somefunction wie folgt definieren:

def someFunction(list1, list2, list3): 
    i=random.randint(0,99999) 
    print list1[i], list2[i], list3[i] 

Würde mit bedeuten diese Funktion, dass l1, l2 und l3 wird vollständig für jeden Teilprozess kopiert werden?

Gibt es eine Möglichkeit, dies zu überprüfen?

EDIT2 Nach dem Lesen ein wenig mehr und Überwachung der Gesamtspeicherauslastung des Systems, während Teilprozesse ausgeführt werden, scheint es, dass ganze Objekte tatsächlich für jeden Teilprozess kopiert werden. Und es scheint, weil Referenzzählung.

Die Referenzzählung für l1, l2 und l3 ist in meinem Programm eigentlich nicht notwendig. Dies liegt daran, dass l1, l2 und l3 im Speicher verbleiben (unverändert), bis der Elternprozess beendet wird. Bis dahin muss der von diesen Listen belegte Speicher nicht freigegeben werden. In der Tat weiß ich sicher, dass der Referenzzähler über 0 bleibt (für diese Listen und jedes Objekt in diesen Listen), bis das Programm beendet wird.

Nun wird die Frage, wie kann ich sicherstellen, dass die Objekte nicht in jeden Teilprozess kopiert werden? Kann ich die Referenzzählung für diese Listen und jedes Objekt in diesen Listen möglicherweise deaktivieren?

EDIT3 Nur eine zusätzliche Anmerkung. Unterprozesse müssen l1, l2 und oder irgendwelche Objekte in diesen Listen nicht ändern. Die Teilprozesse müssen nur in der Lage sein, auf einige dieser Objekte zu verweisen, ohne dass der Speicher für jeden Teilprozess kopiert wird.

+0

http://stackoverflow.com/questions/10721915/shared-memory-objects-in-python -multiprocessing Ähnliche Frage und Ihre Antwort. – sean

+0

Erraten durch und noch unsicher über die Antwort. Wird das gesamte Objekt kopiert? Nur ein Teil des Objekts? Nur die Seite mit dem Refcount? Wie kann ich das überprüfen? – FableBlaze

+0

Aufgrund von Copy-on-Write, denke ich, dass Sie nichts Besonderes machen sollten. Warum versuchst du es nicht einfach? – NPE

Antwort

31

Generell gibt es zwei Möglichkeiten, um die gleichen Daten zu teilen:

  • Multithreading
  • Shared Memory

Multithreading nicht geeignet Pythons CPU-gebundene Aufgaben (wegen der GIL für ist), so ist die übliche Lösung in diesem Fall zu gehen multiprocessing. Bei dieser Lösung müssen Sie jedoch die Daten explizit freigeben, indem Sie multiprocessing.Value und multiprocessing.Array verwenden.

Beachten Sie, dass die gemeinsame Nutzung von Daten zwischen Prozessen möglicherweise nicht die beste Wahl ist, da alle Synchronisierungsprobleme auftreten. Ein Ansatz, bei dem Akteure Nachrichten austauschen, wird in der Regel als bessere Wahl angesehen. Siehe auch Python documentation:

Wie oben erwähnt, wenn die gleichzeitige Programmierung tut es in der Regel ist am besten gemeinsam genutzten Zustand zu vermeiden, mit so weit wie möglich. Das ist besonders wahr, wenn mehrere Prozesse verwendet werden. Wenn Sie wirklich einige gemeinsame Daten verwenden müssen, bietet Multiprocessing mehrere Möglichkeiten.

In Ihrem Fall müssen Sie l1, l2 und l3 in gewisser Weise verständlich multiprocessing (zum Beispiel durch ein multiprocessing.Array verwendet wird) wickeln und sie dann als Parameter übergeben.
Beachten Sie auch, dass Sie, wie Sie sagten, keinen Schreibzugriff benötigen, dann sollten Sie beim Erstellen der Objekte lock=False übergeben, oder alle Zugriffe werden weiterhin serialisiert.

+0

Kann ich 'multiprocessing.Array' verwenden, um Listen beliebiger Objekte wie' bitarray() 'zu umbrechen? – FableBlaze

+0

@ anti666: Ich denke, Sie sollten 'multiprocessing.sharedctypes' verwenden - siehe http://docs.python.org/2/library/multiprocessing.html#module-multiprocessing.sharedctypes –

+1

Alternativ, wenn bitarray den Protokollpuffer unterstützt, Sie könnte es als Bytearray teilen und es dann wieder in ein Bitarray in den erzeugten Prozessen konvertieren. –

9

Wenn Sie die Copy-on-Write-Funktion verwenden möchten und Ihre Daten statisch sind (unverändert in untergeordneten Prozessen), sollten Sie Python nicht mit Speicherblöcken belegen, in denen Ihre Daten liegen. Sie können dies leicht tun, indem Sie C- oder C++ - Strukturen (z. B. stl) als Container verwenden und eigene Python-Wrapper bereitstellen, die Zeiger auf Datenspeicher verwenden (oder möglicherweise Datenspeicher kopieren), wenn Objekte auf Python-Ebene erstellt werden . All dies kann sehr einfach mit fast Python Einfachheit und Syntax mit cython getan werden.

 
# pseudo cython 
cdef class FooContainer: 
    cdef char * data 
    def __cinit__(self, char * foo_value): 
     self.data = malloc(1024, sizeof(char)) 
     memcpy(self.data, foo_value, min(1024, len(foo_value))) 

    def get(self): 
     return self.data 

 
# python part 
from foo import FooContainer 

f = FooContainer("hello world") 
pid = fork() 
if not pid: 
    f.get() # this call will read same memory page to where 
      # parent process wrote 1024 chars of self.data 
      # and cython will automatically create a new python string 
      # object from it and return to caller 

Der obige Pseudocode schlecht geschrieben wird. Benutze es nicht. Anstelle von self.data sollte in Ihrem Fall C- oder C++ - Container sein.

0

Sie Memcached oder redis und stellen jeweils als ein Schlüsselwertpaar verwenden können { 'l1' ...