Ich habe untersucht, wie sich die Ausführungsgeschwindigkeit ändert, je nachdem, wie Cython-Funktionen geschrieben werden.
# | Faktor | Entscheidungen |
---|---|---|
1 | Zu verwendende Syntax | ① Python für Anweisung ② Cython für Anweisung |
2 | Argumenttyp | (1) Beliebig iterierbar (2) Numpy Array (3) Typisierte Speicheransicht |
3 | Spezifikation des Argumentelementtyps | ① Keine ② Ja |
4 | Elementzugriffsmethode | ① Nicht-Index-Spezifikation (v in vs) ② Index-Spezifikation (vs)[i]) |
5 | Vorhandensein oder Nichtvorhandensein einer Prüffunktion | ① Keine ② Ja |
`für i in range (vs.shape [0]) schreiben:`
und `vs. [i] Es scheint besser, Indexzugriff in Form von
** zu haben. ** Sie sollten vermeiden, ``
für v in vs:` `` zu schreiben, als wäre es eine Listeneinbeziehung **.Nehmen Sie die Summe der Quadrate der nächsten Anzahl von Spalten. Bereiten Sie zwei Versionen der Python-Liste und des Numpy-Arrays vor.
python
import numpy as np
vs_list = [1.0,]*10**6
vs_ndarray = np.ones((10**6,), dtype=np.double)
Die Leistung der ursprünglichen Python for-Schleife war wie folgt [^ 2]. Es dauert 300-400 ms, um die Summe der Quadrate von $ 10 ^ 6 $ reellen Zahlen zu erhalten [^ 3]. ** In Python scheint es schneller zu sein, den Iterator gehorsam ohne Indexzugriff zu verwenden **.
# | Syntax | Argumenttyp | Elementtypspezifikation | Elementzugriff | Funktion prüfen | Ausführungszeit[ms] |
---|---|---|---|---|---|---|
1 | Python für Anweisung | Beliebig iterierbar | Keiner | v in vs | Ja | 302 |
2 | Python für Anweisung | Beliebig iterierbar | Keiner | vs[i] | Ja | 381 |
1
def sum_py(vs):
s = 0
for v in vs:
s += v
return s
python
%timeit square_sum_py(vs_list)
2
def square_sum_py_range(vs):
s = 0
for i in range(len(vs)):
s += vs[i]**2
return s
python
%timeit square_sum_py_range(vs_list)
Es überrascht nicht, dass Numpys Sendung die Dinge dramatisch beschleunigen kann. Cython wird jedoch tatsächlich verwendet, wenn die Broadcast-Funktion nicht verwendet werden kann. Daher müssen hier andere Methoden in Betracht gezogen werden. Sie können sehen, dass die Verarbeitung eines Numpy-Arrays mit einer Python for-Anweisung es verlangsamt.
# | Syntax | Argumenttyp | Elementtypspezifikation | Elementzugriff | Funktion prüfen | Ausführungszeit[ms] |
---|---|---|---|---|---|---|
3 | Numpy Sendung | Numpy Array | Ja | Keine (unnötig) | Ja | 17 |
4 | Python für Anweisung | Numpy Array | Ja | v in vs | Ja | 1640 |
5 | Python für Anweisung | Numpy Array | Ja | vs[i] | Ja | 1950 |
3
def square_sum_np(vs):
return np.sum(vs**2)
python
%timeit square_sum_np(vs_ndarray)
4
#Funktion oben definiert
%timeit square_sum_py(vs_ndarray)
5
#Funktion oben definiert
%timeit square_sum_py_range(vs_ndarray)
Lassen Sie uns als nächstes über die Verwendung von Cython nachdenken, um Pythons for-Anweisung zu beschleunigen. Die Möglichkeit, ein auf Speicher zugängliches Array auf niedriger Ebene an eine Funktion in Cython zu übergeben, besteht darin, das Numpy-Array direkt als Argument anzugeben (http://docs.cython.org/en/latest/src/tutorial/numpy). Es gibt .html) und So geben Sie eine typisierte Speicheransicht als Argument an. Hier beginnen wir mit der intuitiveren früheren Methode. Wenn man sich die fragmentarischen Informationen im Netz ansieht, scheint es, dass nur die Typspezifikation für die Beschleunigung mit Cython effektiv ist. Wie Sie in der folgenden Tabelle sehen können, ist die Angabe der Funktionsargumente in einem Numpy-Array jedoch nicht viel schneller als der ursprüngliche Python-Code (1) (6). Zu diesem Zeitpunkt ist die Angabe der Elementtypen des Numpy-Arrays wenig sinnvoll (7). Natürlich ist es mit Numpy-Arrays schneller als Python-Code (4), aber das ist alles, es gibt keinen Grund, Cython zu verwenden.
# | Syntax | Argumenttyp | Elementtypspezifikation | Elementzugriff | Funktion prüfen | Ausführungszeit[ms] |
---|---|---|---|---|---|---|
6 | Cython zur Aussage | Numpy Array | Keiner | v in vs | Ja | 378 |
7 | Cython zur Aussage | Numpy Array | Ja | v in vs | Ja | 362 |
6
%%cython
cimport numpy as np
def square_sum_cy_np(np.ndarray vs):
cdef double v, s = 0.0
for v in vs:
s += v**2
return s
python
%timeit square_sum_cy_np(vs_ndarray)
7
%%cython
cimport numpy as np
def square_sum_cy_np_typed(np.ndarray[np.double_t, ndim=1] vs):
cdef double v, s = 0.0
for v in vs:
s += v**2
return s
python
%timeit square_sum_cy_np_typed(vs_ndarray)
Anschließend müssen Sie die Zugriffsmethode (wie die for-Anweisung geschrieben wird) in das Array-Element ändern. Der Indexzugriff ohne Elementtypisierung ist langsamer, der Indexzugriff mit Elementtypisierung jedoch erheblich schneller.
# | Syntax | Argumenttyp | Elementtypspezifikation | Elementzugriff | Funktion prüfen | Ausführungszeit[ms] |
---|---|---|---|---|---|---|
8 | Cython zur Aussage | Numpy Array | Keiner | vs[i] | Ja | 1610 |
9 | Cython zur Aussage | Numpy Array | Ja | vs[i] | Ja | 28 |
8
%%cython
cimport numpy as np
def square_sum_cy_np_range(np.ndarray vs):
cdef double s = 0.0
for i in range(vs.shape[0]):
s += vs[i]**2
return s
python
%timeit square_sum_cy_np_range(vs_ndarray)
9
%%cython
cimport numpy as np
def square_sum_cy_np_typed_range(np.ndarray[np.double_t, ndim=1] vs):
cdef double s = 0.0
for i in range(vs.shape[0]):
s += vs[i]**2
return s
python
%timeit square_sum_cy_np_typed_range(vs_ndarray)
In der offiziellen Dokumentation wird auch beschrieben, wie die Überprüfungsfunktion für den Array-Zugriff (10-13) weggelassen wird. Der Unterschied zum Nichtauslassen der Prüffunktion (6-9) war jedoch gering. Es ist wahr, dass 10 bis 20% schneller sind, aber es ist nur so effektiv wie der letzte Stoß.
# | Syntax | Argumenttyp | Elementtypspezifikation | Elementzugriff | Funktion prüfen | Ausführungszeit[ms] |
---|---|---|---|---|---|---|
10 | Cython zur Aussage | Numpy Array | Keiner | v in vs | Keiner | 315 |
11 | Cython zur Aussage | Numpy Array | Ja | v in vs | Keiner | 313 |
12 | Cython zur Aussage | Numpy Array | Keiner | vs[i] | Keiner | 1610 |
13 | Cython zur Aussage | Numpy Array | Ja | vs[i] | Keiner | 25 |
10
%%cython
cimport numpy as np
from cython import boundscheck, wraparound
def square_sum_cy_np_nocheck(np.ndarray vs):
cdef double v, s = 0.0
with boundscheck(False), wraparound(False):
for v in vs:
s += v**2
return s
python
%timeit square_sum_cy_np_nocheck(vs_ndarray)
11
%%cython
cimport numpy as np
from cython import boundscheck, wraparound
def square_sum_cy_np_typed_nocheck(np.ndarray[np.double_t, ndim=1] a):
cdef double d, s = 0.0
with boundscheck(False), wraparound(False):
for d in a:
s += d**2
return s
python
%timeit square_sum_cy_np_typed_nocheck(vs_ndarray)
12
%%cython
cimport numpy as np
from cython import boundscheck, wraparound
def square_sum_cy_np_range_nocheck(np.ndarray a):
cdef double s = 0.0
with boundscheck(False), wraparound(False):
for i in range(a.shape[0]):
s += a[i]**2
return s
python
%timeit square_sum_cy_np_range_nocheck(vs_ndarray)
13
%%cython
cimport numpy as np
from cython import boundscheck, wraparound
def square_sum_cy_np_typed_range_nocheck(np.ndarray[np.double_t, ndim=1] a):
cdef double s = 0.0
with boundscheck(False), wraparound(False):
for i in range(a.shape[0]):
s += a[i]**2
return s
python
%timeit square_sum_cy_np_typed_range_nocheck(vs_ndarray)
Eine andere Möglichkeit, ein auf Speicher zugängliches Array auf niedriger Ebene an die Cython-Funktion zu übergeben, besteht darin, eine typisierte Speicheransicht als Argument anzugeben. Diese Methode wird in den offiziellen Dokumenten empfohlen. Wie Sie den folgenden Ergebnissen entnehmen können, funktioniert in diesem Fall auch die Methode für den Zugriff auf die Array-Elemente.
# | Syntax | Argumenttyp | Elementtypspezifikation | Elementzugriff | Funktion prüfen | Ausführungszeit[ms] |
---|---|---|---|---|---|---|
14 | Cython zur Aussage | Typisierte Speicheransicht | Ja (notwendig) | v in vs | Ja | 519 |
15 | Cython zur Aussage | Typisierte Speicheransicht | Ja (notwendig) | vs[i] | Ja | 26 |
16 | Cython zur Aussage | Typisierte Speicheransicht | Ja (notwendig) | v in vs | Keiner | 516 |
17 | Cython zur Aussage | Typisierte Speicheransicht | Ja (notwendig) | vs[i] | Keiner | 24 |
14
%%cython
def square_sum_cy_mv(double[:] vs):
cdef double v, s = 0.0
for v in vs:
s += v**2
return s
python
%timeit square_sum_cy_np_typed_range_nocheck(vs_ndarray)
15
%%cython
def square_sum_cy_mv_range(double[:] vs):
cdef double s = 0.0
for i in range(vs.shape[0]):
s += vs[i]**2
return s
python
%timeit square_sum_cy_mv_range(vs_ndarray)
16
%%cython
from cython import boundscheck, wraparound
def square_sum_cy_mv_nocheck(double[:] vs):
cdef double v, s = 0.0
with boundscheck(False), wraparound(False):
for v in vs:
s += v**2
return s
python
%timeit square_sum_cy_mv_nocheck(vs_ndarray)
17
%%cython
from cython import boundscheck, wraparound
def square_sum_cy_mv_range_nocheck(double[:] vs):
cdef double s = 0.0
with boundscheck(False), wraparound(False):
for i in range(vs.shape[0]):
s += vs[i]**2
return s
python
%timeit square_sum_cy_mv_range_nocheck(vs_ndarray)
python
import numpy as np
%load_ext Cython
python
vs = np.ones((10**3,10**3), dtype=np.double)
python
%%cython
from cython import boundscheck, wraparound
cdef double square_sum(double[:, :] vs):
# def square_sum(double[:, :] vs):Auch wenn es in diesem Fall fast gleich ist
cdef:
double s = 0.0
Py_ssize_t nx = vs.shape[0]
Py_ssize_t ny = vs.shape[1]
Py_ssize_t i, j
with boundscheck(False), wraparound(False):
for i in range(nx):
for j in range(ny):
s += vs[i, j]**2
return s
python
%timeit square_sum(vs)
[^ 1]: Die Anzahl der Codes beträgt nicht $ 2 \ times3 \ times2 \ times2 \ times2 = 24 $, da (1) eine Korrelation zwischen den Auswahlmöglichkeiten besteht und (2) der Referenzcode hinzugefügt wird. Es ist die Ursache. [^ 2]: Es gab eine maximale Abweichung von ± 20% der gemessenen Werte um% timeit, aber hier sind wir besorgt über den Unterschied von mehreren bis mehreren zehn Mal, also bin ich besorgt über den Unterschied von mehreren Prozent. Ich werde nicht. [^ 3]: Die CPU verwendet Intel Celeron.
Recommended Posts