python numpy multiprocessing shared-memory

python - Comparta una matriz Numpy grande y de solo lectura entre los procesos de multiprocesamiento



multiprocessing shared-memory (5)

Tengo una Matriz SciPy de 60GB (Matriz). Debo compartir entre más de 5 objetos de Process multiprocessing . He visto numpy-sharedmem y leo esta discusión en la lista de SciPy. Parece haber dos enfoques: numpy-sharedmem y el uso de multiprocessing.RawArray() y el mapeo de los dtype NumPy a ctype s. Ahora, numpy-sharedmem parece ser el camino a seguir, pero aún no he visto un buen ejemplo de referencia. No necesito ningún tipo de bloqueos, ya que el conjunto (en realidad, una matriz) será de solo lectura. Ahora, debido a su tamaño, me gustaría evitar una copia. Parece que el método correcto es crear la única copia de la matriz como una matriz sharedmem , y luego pasarla a los objetos Process ? Un par de preguntas específicas:

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

  2. En la discusión que he vinculado anteriormente, ¿se menciona que numpy-sharedmem no está protegido contra 64 bits? Definitivamente estoy usando algunas estructuras que no son de 32 bits direccionables.

  3. ¿Hay un compromiso con el enfoque RawArray() ? Más lento, más caprichoso?

  4. ¿Necesito un mapeo ctype-to-dtype para el método numpy-sharedmem?

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

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

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


@Velimir Mlaker dio una gran respuesta. Pensé que podría agregar algunos fragmentos de 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 las asas cuando el subproceso está comenzando o después de que haya comenzado? Si es solo lo primero, puede usar el target y args argumentos para el Process . Esto es potencialmente mejor que usar una variable global.
  2. Desde la página de discusión que vinculó, parece que el soporte para Linux de 64 bits se agregó a sharedmem hace un tiempo, por lo que podría no ser un problema.
  3. No sé sobre este.
  4. No. Consulte el ejemplo a continuación.

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 podría ser útil.


Puede que le interese 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 las matrices ( numpy o sharedmem ) a los procesos engendrados, a través de Pipe. Los trabajadores simplemente llaman a sum() sobre 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 nueva API cv2 OpenCV. Las imágenes, en realidad las referencias de las mismas, se comparten entre los procesos a través del objeto de diccionario creado a partir de multiprocessing.Manager (en lugar de usar Cola o Pipe). Obtuve grandes mejoras de rendimiento en comparación con el uso de matrices simples de NumPy.

Pipe vs. Queue :

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. Pero si solo tiene dos procesos hablando de ida y vuelta, es seguro usar Pipe o, como dicen los documentos:

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

seguridad sharedmem :

El problema principal con el módulo sharedmem es la posibilidad de pérdida de memoria tras la desagradable salida del programa. Esto se describe en una larga discusión aquí . Aunque el 10 de abril de 2011, Sturla menciona una solución para la pérdida de memoria, todavía he experimentado filtraciones desde entonces, usando ambos repositorios, el de Sturla Molden en GitHub ( github.com/sturlamolden/sharedmem-numpy ) y Chris Lee-Messer en Bitbucket ( numpy-sharedmem ).


Si está en Linux (o en cualquier sistema compatible con POSIX), puede definir esta matriz como una variable global. multiprocessing está utilizando fork() en Linux cuando comienza un nuevo proceso hijo. Un proceso hijo generado recientemente comparte automáticamente la memoria con su padre siempre que no la modifique (mecanismo de copy-on-write ).

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

No transfiera su matriz al constructor Process() , esto instruirá multiprocessing para pickle los datos para el niño, lo que sería extremadamente ineficiente o imposible en su caso. En Linux, justo después de fork() el hijo es una copia exacta del padre que usa la misma memoria física, así que todo lo que necesita hacer es asegurarse de que la variable Python ''que contiene'' la matriz sea accesible desde la función target que usted entrega a Process() . Esto normalmente 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 admite fork() , el multiprocessing utiliza la llamada a la API win32 CreateProcess . Crea un proceso completamente nuevo a partir de cualquier ejecutable dado. Es por eso que en Windows se requiere uno para extraer datos para el niño si uno necesita datos que se han creado durante el tiempo de ejecución del padre.


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

Creando la matriz:

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

A continuación, puede llenar 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 elimina la variable a .

Más adelante puede usar 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:

  • Trabajando con Big Data en python y numpy, no hay suficiente RAM, ¿cómo guardar resultados parciales en el disco?

  • ¿Es posible mapear datos discontiuos en un disco a una matriz con Python?


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