¿Por qué no es numpy.mean multiproceso?

He estado buscando formas de multiproceder fácilmente algunos de mis códigos de análisis simples desde que me di cuenta de que solo usaba un núcleo, a pesar de que se supone que tiene varios subprocesos.

Sé que numpy está configurado para múltiples núcleos, ya que puedo ver que las pruebas que usan numpy.dot usan todos mis núcleos, por lo que simplemente reimplementé la media como un producto de puntos y se ejecuta mucho más rápido. ¿Hay alguna razón para que no pueda correr tan rápido por sí solo? Encuentro un comportamiento similar para matrices más grandes, aunque la proporción es cercana a 2 que la 3 que se muestra en mi ejemplo.

He estado leyendo un montón de publicaciones sobre problemas similares de velocidad numpy, y al parecer es mucho más complicado de lo que hubiera pensado. Cualquier información sería útil, preferiría usar solo medio porque es más legible y menos código, pero podría cambiar a medios basados ​​en puntos.

In [27]: data = numpy.random.rand(10,10) In [28]: a = numpy.ones(10) In [29]: %timeit numpy.dot(data,a)/10.0 100000 loops, best of 3: 4.8 us per loop In [30]: %timeit numpy.mean(data,axis=1) 100000 loops, best of 3: 14.8 us per loop In [31]: numpy.dot(data,a)/10.0 - numpy.mean(data,axis=1) Out[31]: array([ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 1.11022302e-16, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, -1.11022302e-16]) 

He estado buscando formas de multiproceder fácilmente algunos de mis códigos de análisis simples desde que me di cuenta de que solo usaba un núcleo, a pesar de que se supone que tiene varios subprocesos.

¿Quién dice que se supone que es multihilo?

numpy está diseñado principalmente para ser lo más rápido posible en un solo núcleo, y para ser lo más paralelizable posible si necesita hacerlo. Pero todavía tienes que paralelizarlo.

En particular, puede operar con subobjetos independientes al mismo tiempo, y las operaciones lentas liberan el GIL cuando es posible, aunque “cuando sea posible” puede que no sea suficiente. Además, los objetos numpy están diseñados para ser compartidos o pasados ​​entre procesos tan fácilmente como sea posible, para facilitar el uso del multiprocessing .

Hay algunos métodos especializados que son paralelizados automáticamente, pero la mayoría de los métodos básicos no lo son. En particular, el dot se implementa sobre BLAS cuando es posible, y BLAS se paraliza automáticamente en la mayoría de las plataformas, pero la mean se implementa en código C simple.

Ver Progtwigción paralela con numpy y scipy para más detalles.


Entonces, ¿cómo sabes qué métodos están paralelos y cuáles no? Y, de los que no lo son, ¿cómo sabe cuáles pueden ser bien enhebrados manualmente y cuáles necesitan multiprocesamiento?

No hay una buena respuesta para eso. Puede hacer suposiciones informadas (X parece que probablemente se haya implementado sobre ATLAS, y mi copia de ATLAS está implícitamente enlazada), o puede leer la fuente.

Pero generalmente, lo mejor que puedes hacer es probarlo y probarlo. Si el código está usando el 100% de un núcleo y el 0% de los otros, agregue hilos manuales. Si ahora usa el 100% de un núcleo y el 10% de los otros y apenas corre más rápido, cambie el multiproceso al multiproceso. (Afortunadamente, Python lo hace bastante fácil, especialmente si usas las clases Executor de concurrent.futures o las clases de Pool de multiprocessing . Pero a menudo aún necesitas pensar en ello y probar los costos relativos de compartir vs. pasar si tienes matrices grandes)

Además, como señala kwatford, solo porque un método no parezca implícitamente paralelo no significa que no sea paralelo en la próxima versión de numpy, o la próxima versión de BLAS, o en una plataforma diferente, o incluso en una máquina con cosas ligeramente diferentes instaladas en ella. Por lo tanto, prepárate para volver a probar. Y haga algo como my_mean = numpy.mean y luego use my_mean todas partes, para que pueda cambiar una línea a my_mean = pool_threaded_mean .

Básicamente, debido a que la biblioteca BLAS tiene un producto de puntos optimizado que pueden llamar fácilmente para dot que son inherentemente paralelos. Admiten que podrían ampliar sus números para paralelizar otras operaciones, pero optaron por no ir por ese camino. Sin embargo, brindan varios consejos sobre cómo paralelizar su código numpy (básicamente para dividir el trabajo entre N núcleos (por ejemplo, N = 4), dividir su matriz en N subarreglas y enviar trabajos para cada sub-matriz a su propio hilo y entonces combina tus resultados).

Ver http://wiki.scipy.org/ParallelProgramming :

Utilizar primitivas paralelas.

Una de las grandes fortalezas de los números es que puede express las operaciones de matriz de forma muy limpia. Por ejemplo, para calcular el producto de la matriz A y la matriz B, simplemente haga lo siguiente:

>>> C = numpy.dot(A,B)

Esto no solo es simple y claro de leer y escribir, ya que Numpy sabe que desea hacer un producto de puntos matriciales, puede usar una implementación optimizada obtenida como parte de “BLAS” (las subrutinas de álgebra lineal básica). Normalmente, esta será una biblioteca cuidadosamente ajustada para que se ejecute lo más rápido posible en su hardware aprovechando la memoria caché y la implementación del ensamblador. Pero muchas architectures ahora tienen un BLAS que también aprovecha una máquina multinúcleo. Si su numpy / scipy se comstack utilizando uno de estos, entonces dot () se computará en paralelo (si es más rápido) sin que usted haga nada. De manera similar para otras operaciones matriciales, como la inversión, la descomposición de valores singulares, el determinante, etc. Por ejemplo, la biblioteca de código abierto ATLAS permite la selección del tiempo de comstackción del nivel de paralelismo (número de subprocesos). La biblioteca de MKL patentada de Intel ofrece la posibilidad de elegir el nivel de paralelismo en tiempo de ejecución. También está la biblioteca GOTO que permite la selección en tiempo de ejecución del nivel de paralelismo. Este es un producto comercial, pero el código fuente se distribuye de forma gratuita para uso académico.

Finalmente, scipy / numpy no paraliza operaciones como

>>> A = B + C

>>> A = numpy.sin(B)

>>> A = scipy.stats.norm.isf(B)

Estas operaciones se ejecutan de forma secuencial, sin aprovechar las ventajas de las máquinas multinúcleo (pero ver más abajo). En principio, esto podría cambiarse sin demasiado trabajo. OpenMP es una extensión del lenguaje C que permite a los comstackdores producir código de paralelización para bucles anotados adecuadamente (y otras cosas). Si alguien se sentó y anotó unos cuantos bucles centrales en numpy (y posiblemente en scipy), y si uno compiló numpy / scipy con OpenMP activado, los tres anteriores se ejecutarán automáticamente en paralelo. Por supuesto, en realidad, uno desearía tener algún control de tiempo de ejecución; por ejemplo, uno podría desear desactivar la paralelización automática si estuviera planeando ejecutar varios trabajos en la misma máquina multiprocesador.