python - Memoria compartida en multiprocesamiento
multiprocessing shared-memory (3)
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))
¿Significa esto que l1, l2 y l3 se copiarán 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 se devolverán al proceso principal. Las listas l1, l2 y l3 no serán modificadas por alguna función.
Por lo tanto, supongo que los subprocesos no necesitan y no copiarían estas enormes listas, sino que simplemente las compartirían con el padre. Lo que significa que el programa tomaría 16 GB de RAM (independientemente de cuántos subprocesos inicie) debido al enfoque de copiado bajo escritura en Linux. ¿Estoy en lo correcto o me estoy perdiendo algo que podría hacer que las listas sean copiadas?
EDITAR : Todavía estoy confundido, después de leer un poco más sobre el tema. Por un lado, Linux usa copy-on-write, lo que debería significar que no se copian datos. Por otro lado, acceder al objeto cambiará su recuento de ref (aún no estoy seguro de por qué y qué significa eso). Aun así, ¿se copiará todo el objeto?
Por ejemplo, si defino someFunction 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 significa que l1, l2 y l3 se copiarán por completo para cada subproceso?
¿Hay alguna manera de verificar esto?
EDIT2 Después de leer un poco más y monitorear el uso total de la memoria del sistema mientras se ejecutan los subprocesos, parece que de hecho se copian objetos enteros para cada subproceso. Y parece ser debido al conteo de referencias.
El recuento de referencias para l1, l2 y l3 en realidad no es necesario en mi programa. 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 referencias se mantendrá por encima de 0 (para estas listas y para cada objeto en estas listas) hasta que el programa finalice.
Entonces, ahora surge la pregunta: ¿cómo puedo asegurarme de que los objetos no se copiarán en cada subproceso? ¿Puedo quizás desactivar el conteo de referencias para estas listas y cada objeto en estas listas?
EDIT3 Solo una nota adicional. Los subprocesos no necesitan modificar l1
, l2
y l3
ni ningún objeto en estas listas. Los subprocesos solo necesitan poder hacer referencia a algunos de estos objetos sin causar que la memoria se copie para cada subproceso.
En términos generales, hay dos formas de compartir los mismos datos:
- Multihilo
- Memoria compartida
El multihilo de Python no es adecuado para tareas vinculadas a CPU (debido a GIL), por lo que la solución habitual en ese caso es continuar multiprocessing
. Sin embargo, con esta solución debe compartir los datos de forma explícita, utilizando multiprocessing.Array
. multiprocessing.Value
y multiprocessing.Array
. 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 los actores intercambiando mensajes generalmente se ve como una mejor opción. Ver también documentación de Python :
Como se mencionó anteriormente, al hacer una programación concurrente, generalmente es mejor evitar el uso de estado compartido en la medida de lo posible. Esto es particularmente cierto cuando se usan procesos múltiples.
Sin embargo, si realmente necesita usar algunos datos compartidos, el multiprocesamiento proporciona algunas formas de hacerlo.
En su caso, necesita ajustar l1
, l2
y l3
de alguna manera comprensible mediante multiprocessing
(por ejemplo, mediante el uso de un multiprocessing.Array
), y luego pasarlos como parámetros.
Tenga en cuenta también que, como dijo, no necesita acceso de escritura, entonces debe pasar lock=False
mientras crea los objetos, o todos los accesos seguirán serializados.
Puede usar memcached o redis y establecer cada uno como un par de valores clave {''l1'' ...
Si desea hacer uso de la característica de copiar por 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 proporcionar sus propios envoltorios de python que usarán punteros a la memoria de datos (o posiblemente copiarán datos de la memoria) cuando se creará un objeto a nivel de pitón, si es que hay alguno . Todo esto se puede hacer muy fácil con la simplicidad y sintaxis casi python con cython .
# 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
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.