Memoria compartida en multiprocesamiento.

Tengo tres listas grandes. Primero contiene bitarrays (módulo bitarray 0.8.0) y los otros dos contienen matrices de enteros.

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

Estas estructuras de datos requieren bastante RAM (~ 16 GB en total).

Si comienzo 12 subprocesos usando:

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

¿Esto significa que se copiarán l1, l2 y l3 para cada subproceso o los subprocesos compartirán estas listas? O para ser más directo, ¿usaré 16GB o 192GB de RAM?

someFunction leerá algunos valores de estas listas y luego realizará algunos cálculos basados ​​en los valores leídos. Los resultados serán devueltos al proceso padre. Las listas l1, l2 y l3 no serán modificadas por alguna función.

Por lo tanto, asumo que los subprocesos no necesitan y no copiarían estas enormes listas, sino que simplemente los compartirían con los padres. ¿Significa que el progtwig tomaría 16 GB de RAM (independientemente de cuántos subprocesos inicie) debido al enfoque de copia en escritura en Linux? ¿Estoy en lo correcto o me falta algo que pueda hacer que se copien las listas?

EDIT : Todavía estoy confundido, después de leer un poco más sobre el tema. Por un lado, Linux utiliza copia en escritura, lo que debería significar que no se copian datos. Por otro lado, el acceso al objeto cambiará su recuento de referencia (todavía no estoy seguro de por qué y qué significa eso). Aun así, ¿se copiará todo el objeto?

Por ejemplo, si defino algunas funciones de la siguiente manera:

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

¿El uso de esta función significaría que l1, l2 y l3 se copiarán completamente para cada subproceso?

¿Hay una manera de comprobar esto?

EDIT2 Después de leer un poco más y monitorear el uso total de memoria del sistema mientras se ejecutan los subprocesos, parece que se copian objetos completos para cada subproceso. Y parece que es porque la cuenta de referencia.

El recuento de referencias para l1, l2 y l3 no es necesario en mi progtwig. Esto se debe a que l1, l2 y l3 se mantendrán en la memoria (sin cambios) hasta que finalice el proceso principal. No hay necesidad de liberar la memoria utilizada por estas listas hasta entonces. De hecho, estoy seguro de que el recuento de referencia permanecerá por encima de 0 (para estas listas y todos los objetos en estas listas) hasta que el progtwig salga.

Entonces, ahora la pregunta es: ¿cómo puedo asegurarme de que los objetos no se copiarán en cada subproceso? ¿Quizás puedo deshabilitar el conteo de referencias para estas listas y cada objeto en estas listas?

EDIT3 Sólo una nota adicional. Los subprocesos no necesitan modificar l1 , l2 y l3 o cualquier objeto en estas listas. Los subprocesos solo necesitan poder hacer referencia a algunos de estos objetos sin hacer que la memoria se copie para cada subproceso.

En términos generales, hay dos formas de compartir los mismos datos:

  • Multihilo
  • Memoria compartida

El subprocesamiento múltiple de Python no es adecuado para tareas vinculadas a la CPU (debido a la GIL), por lo que la solución habitual en ese caso es continuar con el multiprocessing . Sin embargo, con esta solución necesita compartir explícitamente los datos, utilizando multiprocessing.Value y multiprocessing.Array .

Tenga en cuenta que, por lo general, compartir datos entre procesos puede no ser la mejor opción, debido a todos los problemas de sincronización; Un enfoque que involucra a actores que intercambian mensajes se ve generalmente como una mejor opción. Véase también la documentación de Python :

Como se mencionó anteriormente, cuando se realiza una progtwigción concurrente, generalmente es mejor evitar el uso del estado compartido en la medida de lo posible. Esto es particularmente cierto cuando se utilizan múltiples procesos.

Sin embargo, si realmente necesita utilizar algunos datos compartidos, el multiprocesamiento proporciona un par de formas de hacerlo.

En su caso, necesita ajustar l1 , l2 y l3 de alguna manera comprensible mediante multiprocessing (por ejemplo, mediante un multiprocessing.Array . multiprocessing.Array ) y luego pasarlos como parámetros.
Tenga en cuenta también que, como dijo que no necesita acceso de escritura, debe pasar lock=False al crear los objetos, o todo el acceso seguirá siendo serializado.

Si desea utilizar la función de copia en escritura y sus datos son estáticos (sin cambios en los procesos secundarios), debe hacer que Python no se meta con los bloques de memoria donde se encuentran sus datos. Puede hacer esto fácilmente usando estructuras C o C ++ (stl, por ejemplo) como contenedores y proporcione sus propios envoltorios de python que usarán punteros a la memoria de datos (o posiblemente copiar datos mem) cuando se cree un objeto de nivel de python . Todo esto se puede hacer muy fácilmente con la simplicidad y la syntax de python con cython .

 # pseudo cython
 cdef clase FooContainer:
    cdef char * datos
    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):
        volver self.data

 # parte de python
 desde foo import FooContainer

 f = FooContainer ("hola mundo")
 pid = fork ()
 si no es pid:
    f.get () # esta llamada leerá la misma página de memoria donde
            # proceso primario escribió 1024 caracteres de self.data
            # y cython creará automáticamente una nueva cadena de python
            # objeto de él y volver a la persona que llama

El pseudocódigo anterior está mal escrito. No lo uses. En lugar de self.data debe ser un contenedor C o C ++ en su caso.

Puede usar memcached o redis y establecer cada uno como un par de valores clave {‘l1’ …