Wenn das 2d-Array (oder nd-Array) C- oder F-zusammenhängend ist, ist diese Aufgabe, eine Funktion auf ein 2d-Array abzubilden, praktisch dieselbe wie die Aufgabe, eine Funktion auf ein 1d-Array abzubilden - wir nur muss es so sehen, zB via np.ravel(A,'K')
.
Mögliche Lösung für 1d-Array wurde beispielsweise diskutiert hier .
Wenn jedoch der Speicher des 2d-Arrays nicht zusammenhängend ist, ist die Situation etwas komplizierter, da mögliche Cache-Fehler vermieden werden sollen, wenn die Achse in falscher Reihenfolge behandelt wird.
Numpy verfügt bereits über eine Maschinerie, um Achsen in der bestmöglichen Reihenfolge zu verarbeiten. Eine Möglichkeit, diese Maschine zu benutzen, ist np.vectorize
. In der Dokumentation von numpy np.vectorize
heißt es jedoch, dass es "hauptsächlich aus Gründen der Benutzerfreundlichkeit und nicht der Leistung bereitgestellt wird" - eine langsame Python-Funktion bleibt eine langsame Python-Funktion mit dem gesamten damit verbundenen Overhead! Ein weiteres Problem ist der enorme Speicherverbrauch - siehe zum Beispiel diesen SO-Beitrag .
Wenn man eine C-Funktion ausführen möchte, aber die Maschinen von numpy verwenden möchte, ist es eine gute Lösung, numba für die Erstellung von Ufuncs zu verwenden, zum Beispiel:
# 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
Es ist leicht np.vectorize
zu schlagen, aber auch, wenn dieselbe Funktion als Multiplikation / Addition von Numpy-Arrays ausgeführt wird, d. H.
# 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"
Im Anhang dieser Antwort finden Sie den Zeitmesscode:
Numbas Version (grün) ist ungefähr 100-mal schneller als die Python-Funktion (dh np.vectorize
), was nicht überraschend ist. Es ist aber auch etwa zehnmal schneller als die Numpy-Funktionalität, da die Numbas-Version keine Zwischen-Arrays benötigt und somit den Cache effizienter nutzt.
Der ufunc-Ansatz von numba ist zwar ein guter Kompromiss zwischen Benutzerfreundlichkeit und Leistung, aber immer noch nicht das Beste, was wir tun können. Es gibt jedoch keine Silberkugel oder einen Ansatz, der für eine Aufgabe am besten geeignet ist - man muss verstehen, wo die Grenzen liegen und wie sie gemindert werden können.
Zum Beispiel für transzendentale Funktionen (zB exp
, sin
, cos
) numba bietet keine Vorteile gegenüber der numpy np.exp
(es gibt keine temporären Arrays erstellt - die Hauptquelle der Speed-up). Meine Anaconda-Installation verwendet jedoch Intels VML für Vektoren, die größer als 8192 sind - dies ist einfach nicht möglich, wenn der Speicher nicht zusammenhängend ist. Daher ist es möglicherweise besser, die Elemente in einen zusammenhängenden Speicher zu kopieren, um Intels VML verwenden zu können:
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)
Aus Gründen der Fairness des Vergleichs habe ich die Parallelisierung von VML deaktiviert (siehe Code im Anhang):
Wie man sehen kann, wird der Kopieraufwand nach dem Start von VML mehr als ausgeglichen. Sobald jedoch die Daten für den L3-Cache zu groß werden, ist der Vorteil minimal, da die Aufgabe erneut an die Speicherbandbreite gebunden wird.
Auf der anderen Seite könnte numba auch Intels SVML verwenden, wie in diesem Beitrag erläutert :
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)
und Verwendung von VML mit Parallelisierungsausbeuten:
Die Version von numba hat weniger Overhead, aber für einige Größen schlägt VML SVML trotz des zusätzlichen Kopieraufwands - was keine Überraschung ist, da die ufuncs von numba nicht parallelisiert sind.
Auflistungen:
A. Vergleich der Polynomfunktion:
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. Vergleich von 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)',
)