Comparta una gran matriz de solo lectura entre procesos de multiprocesamiento

88

Tengo una matriz SciPy de 60 GB (matriz) que debo compartir entre más de 5 multiprocessing Processobjetos. He visto numpy-sharedmem y leí esta discusión en la lista SciPy. Parece haber dos enfoques: numpy-sharedmemusar multiprocessing.RawArray()ay mapear NumPy dtypesa ctypes. Ahora, numpy-sharedmemparece ser el camino a seguir, pero todavía tengo que ver un buen ejemplo de referencia. No necesito ningún tipo de bloqueos, ya que la matriz (en realidad una matriz) será de solo lectura. Ahora, debido a su tamaño, me gustaría evitar una copia. Se suena como el método correcto es crear la única copia de la matriz como una sharedmemmatriz y, a continuación, pasar a laProcess objetos? Un par de preguntas específicas:

  1. ¿Cuál es la mejor manera de pasar los identificadores de sharedmem a subes Process()? ¿Necesito una cola solo para pasar una matriz? ¿Sería mejor una pipa? ¿Puedo pasarlo como un argumento al Process()init de la subclase (donde supongo que está en escabeche)?

  2. En la discusión que vinculé anteriormente, ¿se menciona que numpy-sharedmemno es seguro para 64 bits? Definitivamente estoy usando algunas estructuras que no son direccionables de 32 bits.

  3. ¿Hay compensaciones en el RawArray()enfoque? ¿Más lento, más buggier?

  4. ¿Necesito alguna asignación ctype-to-dtype para el método numpy-sharedmem?

  5. ¿Alguien tiene un ejemplo de código OpenSource haciendo esto? Soy un aprendiz muy práctico y es difícil hacer que esto funcione sin ningún buen ejemplo que mirar.

Si hay alguna información adicional que pueda proporcionar para ayudar a aclarar esto para otros, comente y la agregaré. ¡Gracias!

Esto debe ejecutarse en Ubuntu Linux y tal vez Mac OS, pero la portabilidad no es una gran preocupación.

Será
fuente
1
Si los diferentes procesos van a escribir en esa matriz, espere multiprocessinghacer una copia de todo para cada proceso.
tiago
3
@tiago: "No necesito ningún tipo de bloqueo, ya que la matriz (en realidad una matriz) será de solo lectura"
Dr. Jan-Philip Gehrcke
1
@tiago: también, multiprocesamiento no es hacer una copia siempre y cuando no se le indique explícitamente (a través de argumentos a target_function). El sistema operativo copiará partes de la memoria de los padres en el espacio de la memoria del niño solo con modificaciones.
Dr. Jan-Philip Gehrcke
Hice algunas preguntas sobre esto antes. Mi solución se puede encontrar aquí: github.com/david-hoffman/peaks/blob/… (lo siento, el código es un desastre).
David Hoffman

Respuestas:

30

@Velimir Mlaker dio una gran respuesta. Pensé que podría agregar algunos comentarios y un pequeño ejemplo.

(No pude encontrar mucha documentación sobre sharedmem; estos son los resultados de mis propios experimentos).

  1. ¿Necesita pasar los identificadores cuando el subproceso se está iniciando o después de que haya comenzado? Si es solo lo primero, puede usar los argumentos targety argsparaProcess . Esto es potencialmente mejor que usar una variable global.
  2. Desde la página de discusión que vinculó, parece que la compatibilidad con Linux de 64 bits se agregó a sharedmem hace un tiempo, por lo que podría no ser un problema.
  3. No sé nada de este.
  4. No. Consulte el ejemplo siguiente.

Ejemplo

#!/usr/bin/env python
from multiprocessing import Process
import sharedmem
import numpy

def do_work(data, start):
    data[start] = 0;

def split_work(num):
    n = 20
    width  = n/num
    shared = sharedmem.empty(n)
    shared[:] = numpy.random.rand(1, n)[0]
    print "values are %s" % shared

    processes = [Process(target=do_work, args=(shared, i*width)) for i in xrange(num)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()

    print "values are %s" % shared
    print "type is %s" % type(shared[0])

if __name__ == '__main__':
    split_work(4)

Salida

values are [ 0.81397784  0.59667692  0.10761908  0.6736734   0.46349645  0.98340718
  0.44056863  0.10701816  0.67167752  0.29158274  0.22242552  0.14273156
  0.34912309  0.43812636  0.58484507  0.81697513  0.57758441  0.4284959
  0.7292129   0.06063283]
values are [ 0.          0.59667692  0.10761908  0.6736734   0.46349645  0.
  0.44056863  0.10701816  0.67167752  0.29158274  0.          0.14273156
  0.34912309  0.43812636  0.58484507  0.          0.57758441  0.4284959
  0.7292129   0.06063283]
type is <type 'numpy.float64'>

Esta pregunta relacionada puede resultar útil.

James Lim
fuente
37

Si está en Linux (o en cualquier sistema compatible con POSIX), puede definir esta matriz como una variable global. multiprocessingusa fork()en Linux cuando inicia un nuevo proceso hijo. Un proceso hijo recién generado comparte automáticamente la memoria con su padre siempre que no lo cambie ( copiar al escribir mecanismo de ).

Dado que está diciendo "No necesito ningún tipo de bloqueos, ya que la matriz (en realidad una matriz) será de solo lectura", aprovechar este comportamiento sería un enfoque muy simple pero extremadamente eficiente: todos los procesos secundarios accederán los mismos datos en la memoria física al leer esta gran matriz numérica.

No entregue su matriz al Process()constructor, esto dará instrucciones multiprocessinga picklelos datos al niño, lo cual sería extremadamente ineficiente o imposible en su caso. En Linux, justo después fork()del hijo hay una copia exacta del padre que usa la misma memoria física, por lo que todo lo que necesita hacer es asegurarse de que la variable de Python 'que contiene' la matriz sea accesible desde la targetfunción que le entregó Process(). Normalmente, esto se puede lograr con una variable 'global'.

Código de ejemplo:

from multiprocessing import Process
from numpy import random


global_array = random.random(10**4)


def child():
    print sum(global_array)


def main():
    processes = [Process(target=child) for _ in xrange(10)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()


if __name__ == "__main__":
    main()

En Windows, que no es compatible fork(), multiprocessingse usa la llamada API win32 CreateProcess. Crea un proceso completamente nuevo a partir de cualquier ejecutable. Es por eso que en Windows se requiere recoger datos para el niño si se necesitan datos que se han creado durante el tiempo de ejecución del padre.

Dr. Jan-Philip Gehrcke
fuente
3
Copy-on-write copiará la página que contiene el contador de referencia (por lo que cada pitón bifurcado tendrá su propio contador de referencia) pero no copiará toda la matriz de datos.
robince
1
Agregaría que he tenido más éxito con las variables de nivel de módulo que con las variables globales ... es decir, agregue la variable a un módulo en el alcance global antes de la bifurcación
robince
5
Una advertencia para las personas que se encuentran con esta pregunta / respuesta: si está utilizando Numpy vinculado a OpenBLAS para su operación multiproceso, asegúrese de deshabilitar su multiproceso (exportar OPENBLAS_NUM_THREADS = 1) cuando el uso de multiprocessingprocesos secundarios podría terminar colgando ( normalmente utilizando 1 / n de un procesador en lugar de n procesadores) al realizar operaciones de álgebra lineal en una matriz / matriz global compartida. El conocido conflicto multiproceso con OpenBLAS parece extenderse a Pythonmultiprocessing
Dologan
1
¿Alguien puede explicar por qué Python no solo usaría el sistema operativo forkpara pasar los parámetros dados Process, en lugar de serializarlos? Es decir, ¿no se podría forkaplicar al proceso principal justo antes de que child se llame, de modo que el valor del parámetro todavía esté disponible en el sistema operativo? ¿Parecería ser más eficiente que serializarlo?
máximo
2
Todos somos conscientes de que fork()no está disponible en Windows, se ha dicho en mi respuesta y varias veces en los comentarios. Sé que esta era su pregunta inicial, y yo contesté cuatro comentarios por encima de esto : "el compromiso es utilizar el mismo método de transferencia de parámetros en ambas plataformas de forma predeterminada, para una mejor capacidad de mantenimiento y para garantizar la igualdad de comportamiento.". Ambas formas tienen sus ventajas y desventajas, por lo que en Python 3 existe una mayor flexibilidad para que el usuario elija el método. Esta discusión no es productiva sin hablar de detalles, lo que no deberíamos hacer aquí.
Dr. Jan-Philip Gehrcke
24

Puede estar interesado en un pequeño fragmento de código que escribí: github.com/vmlaker/benchmark-sharedmem

El único archivo de interés es main.py. Es un punto de referencia de numpy-sharedmem : el código simplemente pasa matrices (ya sea numpyo sharedmem) a procesos generados, a través de Pipe. Los trabajadores simplemente recurren sum()a los datos. Solo estaba interesado en comparar los tiempos de comunicación de datos entre las dos implementaciones.

También escribí otro código más complejo: github.com/vmlaker/sherlock .

Aquí utilizo el módulo numpy-sharedmem para el procesamiento de imágenes en tiempo real con OpenCV; las imágenes son matrices NumPy, según la cv2API más nueva de OpenCV . Las imágenes, en realidad referencias a las mismas, se comparten entre procesos a través del objeto de diccionario creado desde multiprocessing.Manager(en lugar de usar Queue o Pipe). Obtengo grandes mejoras de rendimiento en comparación con el uso de matrices NumPy simples.

Tubería frente a cola :

En mi experiencia, IPC con Pipe es más rápido que Queue. Y eso tiene sentido, ya que Queue agrega bloqueo para que sea seguro para múltiples productores / consumidores. Pipe no lo hace. Pero si solo tiene dos procesos hablando de un lado a otro, es seguro usar Pipe o, como dicen los documentos:

... no hay riesgo de corrupción de procesos que utilizan diferentes extremos de la tubería al mismo tiempo.

sharedmemseguridad :

El principal problema con el sharedmemmódulo es la posibilidad de pérdida de memoria al salir del programa de forma incorrecta. Esto se describe en una extensa discusión aquí . Aunque el 10 de abril de 2011 Sturla menciona una solución a la pérdida de memoria, todavía he experimentado fugas desde entonces, usando ambos repositorios, el de Sturla Molden en GitHub ( github.com/sturlamolden/sharedmem-numpy ) y el de Chris Lee-Messer en Bitbucket ( bitbucket.org/cleemesser/numpy-sharedmem ).

Velimir Mlaker
fuente
Gracias, muy muy informativo. Sin sharedmemembargo, la fuga de memoria parece un gran problema. ¿Alguna pista para resolver eso?
Will el
1
Más allá de solo notar las fugas, no lo he buscado en el código. Agregué a mi respuesta, en "seguridad de sharedmem" arriba, los guardianes de los dos repositorios de código abierto del sharedmemmódulo, como referencia.
Velimir Mlaker
14

Si su matriz es tan grande, puede usar numpy.memmap. Por ejemplo, si tiene una matriz almacenada en el disco, digamos 'test.array', puede usar procesos simultáneos para acceder a los datos en ella incluso en el modo de "escritura", pero su caso es más simple ya que solo necesita el modo de "lectura".

Creando la matriz:

a = np.memmap('test.array', dtype='float32', mode='w+', shape=(100000,1000))

Luego puede completar esta matriz de la misma manera que lo hace con una matriz ordinaria. Por ejemplo:

a[:10,:100]=1.
a[10:,100:]=2.

Los datos se almacenan en el disco cuando borra la variable a.

Posteriormente podrá utilizar múltiples procesos que accederán a los datos en test.array:

# read-only mode
b = np.memmap('test.array', dtype='float32', mode='r', shape=(100000,1000))

# read and writing mode
c = np.memmap('test.array', dtype='float32', mode='r+', shape=(100000,1000))

Respuestas relacionadas:

Saullo GP Castro
fuente
3

También puede resultarle útil echar un vistazo a la documentación de pyro, ya que si puede dividir su tarea de manera adecuada, podría usarla para ejecutar diferentes secciones en diferentes máquinas, así como en diferentes núcleos en la misma máquina.

Steve Barnes
fuente
0

¿Por qué no utilizar subprocesos múltiples? Los recursos del proceso principal pueden ser compartidos por sus subprocesos de forma nativa, por lo que el subproceso múltiple es, obviamente, una mejor manera de compartir objetos que pertenecen al proceso principal.

Si le preocupa el mecanismo GIL de Python, tal vez pueda recurrir al nogilde numba.

Nico
fuente