Quando l'array 2d (o nd-array) è contiguo C o F, allora questo compito di mappare una funzione su un array 2d è praticamente lo stesso del compito di mappare una funzione su un array 1d: devono vederlo in questo modo, ad es np.ravel(A,'K')
. via .
La possibile soluzione per 1d-array è stata discussa per esempio qui .
Tuttavia, quando la memoria del 2d-array non è contigua, la situazione è un po 'più complicata, perché si vorrebbe evitare possibili errori della cache se gli assi vengono gestiti in ordine errato.
Numpy dispone già di un macchinario per elaborare gli assi nel miglior ordine possibile. Una possibilità di usare questo macchinario è np.vectorize
. Tuttavia, la documentazione di numpy np.vectorize
afferma che è "fornita principalmente per comodità, non per prestazioni" - una funzione Python lenta rimane una funzione Python lenta con l'intero sovraccarico associato! Un altro problema è il suo enorme consumo di memoria - vedi ad esempio questo post SO .
Quando si vuole avere una prestazione di una funzione C ma usare il macchinario di numpy, una buona soluzione è usare numba per la creazione di ufuncs, ad esempio:
# 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
Batte facilmente, np.vectorize
ma anche quando la stessa funzione sarebbe eseguita come moltiplicazione / addizione numpy-array, ad es
# 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"
Vedi l'appendice di questa risposta per il codice di misurazione del tempo:
La versione di Numba (verde) è circa 100 volte più veloce della funzione python (cioè np.vectorize
), il che non sorprende. Ma è anche circa 10 volte più veloce della funzionalità numpy, perché la versione numbas non ha bisogno di array intermedi e quindi utilizza la cache in modo più efficiente.
Mentre l'approccio ufunc di numba è un buon compromesso tra usabilità e prestazioni, non è ancora il massimo che possiamo fare. Eppure non esiste un proiettile d'argento o un approccio migliore per qualsiasi attività: bisogna capire quali sono i limiti e come possono essere mitigati.
Ad esempio, per le funzioni trascendentali (ad es exp
. sin
, cos
) Numba non offre alcun vantaggio rispetto a numpy np.exp
(non sono stati creati array temporanei - la fonte principale dell'accelerazione). Tuttavia, la mia installazione di Anaconda utilizza VML di Intel per vettori più grandi di 8192 - semplicemente non può farlo se la memoria non è contigua. Quindi potrebbe essere meglio copiare gli elementi in una memoria contigua per poter usare il VML di 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)
Per correttezza del confronto, ho disattivato la parallelizzazione di VML (vedi codice in appendice):
Come si può vedere, una volta attivato VML, l'overhead della copia è più che compensato. Tuttavia, una volta che i dati diventano troppo grandi per la cache L3, il vantaggio è minimo poiché l'attività diventa di nuovo legata alla larghezza di banda della memoria.
D'altra parte, numba potrebbe usare anche SVML di Intel, come spiegato in questo post :
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)
e usando VML con rese di parallelizzazione:
La versione di numba ha un sovraccarico minore, ma per alcune dimensioni VML batte SVML anche nonostante l'overhead di copiatura aggiuntivo - il che non è un po 'una sorpresa poiché gli ufunc di numba non sono parallelizzati.
Annunci:
A. confronto della funzione polinomiale:
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. confronto di 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)',
)