Cuando el 2d-array (o nd-array) es contiguo en C o F, entonces esta tarea de mapear una función en un 2d-array es prácticamente la misma que la tarea de mapear una función en un 1d-array - simplemente tiene que verlo de esa manera, por ejemplo, a través de np.ravel(A,'K')
.
La posible solución para 1d-array se ha discutido, por ejemplo, aquí .
Sin embargo, cuando la memoria de la matriz 2d no es contigua, entonces la situación es un poco más complicada, porque a uno le gustaría evitar posibles errores de caché si el eje se maneja en el orden incorrecto.
Numpy ya cuenta con una maquinaria para procesar ejes en el mejor orden posible. Una posibilidad para usar esta maquinaria es np.vectorize
. Sin embargo, la documentación de numpy np.vectorize
dice que "se proporciona principalmente por conveniencia, no por rendimiento": ¡una función lenta de Python sigue siendo una función lenta de Python con toda la sobrecarga asociada! Otro problema es su gran consumo de memoria; consulte, por ejemplo, esta publicación SO .
Cuando se quiere tener el rendimiento de una función C pero usar la maquinaria de numpy, una buena solución es usar numba para la creación de ufuncs, por ejemplo:
# runtime generated C-function as ufunc
import numba as nb
@nb.vectorize(target="cpu")
def nb_vf(x):
return x+2*x*x+4*x*x*x
Supera fácilmente, np.vectorize
pero también cuando la misma función se realizaría como multiplicación / suma de matriz numpy, es decir
# numpy-functionality
def f(x):
return x+2*x*x+4*x*x*x
# python-function as ufunc
import numpy as np
vf=np.vectorize(f)
vf.__name__="vf"
Vea el apéndice de esta respuesta para el código de medición de tiempo:
La versión de Numba (verde) es aproximadamente 100 veces más rápida que la función python (es decir np.vectorize
), lo cual no es sorprendente. Pero también es aproximadamente 10 veces más rápido que la funcionalidad numpy, porque la versión numbas no necesita matrices intermedias y, por lo tanto, usa la caché de manera más eficiente.
Si bien el enfoque ufunc de numba es una buena compensación entre usabilidad y rendimiento, todavía no es lo mejor que podemos hacer. Sin embargo, no hay una bala de plata o un enfoque mejor para cualquier tarea: uno tiene que entender cuáles son las limitaciones y cómo se pueden mitigar.
Por ejemplo, para las funciones trascendentales (p exp
. Ej . sin
, cos
) Numba no ofrece ninguna ventaja sobre numpy's np.exp
(no se crean matrices temporales, la fuente principal de la aceleración). Sin embargo, mi instalación de Anaconda utiliza el VML de Intel para vectores mayores que 8192 ; simplemente no puede hacerlo si la memoria no es contigua. Por lo tanto, podría ser mejor copiar los elementos en una memoria contigua para poder usar el VML de Intel:
import numba as nb
@nb.vectorize(target="cpu")
def nb_vexp(x):
return np.exp(x)
def np_copy_exp(x):
copy = np.ravel(x, 'K')
return np.exp(copy).reshape(x.shape)
Para ser justos en la comparación, he desactivado la paralelización de VML (ver código en el apéndice):
Como se puede ver, una vez que se inicia VML, la sobrecarga de la copia está más que compensada. Sin embargo, una vez que los datos se vuelven demasiado grandes para el caché L3, la ventaja es mínima, ya que la tarea vuelve a estar vinculada al ancho de banda de memoria.
Por otro lado, numba también podría usar SVML de Intel, como se explica en esta publicación :
from llvmlite import binding
# set before import
binding.set_option('SVML', '-vector-library=SVML')
import numba as nb
@nb.vectorize(target="cpu")
def nb_vexp_svml(x):
return np.exp(x)
y usando VML con rendimientos de paralelización:
La versión de numba tiene menos sobrecarga, pero para algunos tamaños VML supera a SVML incluso a pesar de la sobrecarga de copia adicional, lo que no es una sorpresa ya que los ufuncs de numba no están paralelos.
Listados:
A. comparación de la función polinómica:
import perfplot
perfplot.show(
setup=lambda n: np.random.rand(n,n)[::2,::2],
n_range=[2**k for k in range(0,12)],
kernels=[
f,
vf,
nb_vf
],
logx=True,
logy=True,
xlabel='len(x)'
)
B. comparación de exp
:
import perfplot
import numexpr as ne # using ne is the easiest way to set vml_num_threads
ne.set_vml_num_threads(1)
perfplot.show(
setup=lambda n: np.random.rand(n,n)[::2,::2],
n_range=[2**k for k in range(0,12)],
kernels=[
nb_vexp,
np.exp,
np_copy_exp,
],
logx=True,
logy=True,
xlabel='len(x)',
)