J'ai étudié comment la vitesse d'exécution change en fonction de la façon d'écrire des fonctions Cython.
# | Facteur | Les choix |
---|---|---|
1 | Syntaxe à utiliser | ① Python pour instruction ② Cython pour instruction |
2 | Type d'argument | (1) Arbitrary iterable (2) Numpy array (3) Affichage de la mémoire typée |
3 | Spécification du type d'élément d'argument | ① Aucun ② Oui |
4 | Méthode d'accès aux éléments | ① Spécification non indexée (v dans vs) ② Spécification d'index (vs)[i]) |
5 | Présence ou absence de fonction de contrôle | ① Aucun ② Oui |
pour i in range (vs.shape [0]):
et `vs [i] Il semble préférable d'avoir un accès à l'index sous la forme de
**. ** Vous devriez éviter d'écrire '' pour v dans vs:
comme s'il s'agissait d'une inclusion de liste **.Prenez la somme des carrés du prochain nombre de colonnes. Préparez deux versions de la liste Python et du tableau Numpy.
python
import numpy as np
vs_list = [1.0,]*10**6
vs_ndarray = np.ones((10**6,), dtype=np.double)
Les performances de la boucle for originale de Python étaient les suivantes [^ 2]. Il faut 300 à 400 ms pour obtenir la somme des carrés de 10 $ ^ 6 $ de nombres réels [^ 3]. ** En Python, il semble plus rapide d'utiliser l'itérateur docilement sans accès à l'index **.
# | syntaxe | Type d'argument | Spécification du type d'élément | Accès aux éléments | Vérifier la fonction | Temps d'exécution[ms] |
---|---|---|---|---|---|---|
1 | Python pour déclaration | Tout itérable | Aucun | v in vs | Oui | 302 |
2 | Python pour déclaration | Tout itérable | Aucun | vs[i] | Oui | 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)
Sans surprise, la diffusion de Numpy peut accélérer considérablement les choses. Cependant, Cython est en fait utilisé lorsque la fonction de diffusion ne peut pas être utilisée, nous devons donc considérer d'autres méthodes ici. Vous pouvez voir que le traitement d'un tableau Numpy avec une instruction Python for le ralentit.
# | syntaxe | Type d'argument | Spécification du type d'élément | Accès aux éléments | Vérifier la fonction | Temps d'exécution[ms] |
---|---|---|---|---|---|---|
3 | Diffusion Numpy | Tableau Numpy | Oui | Aucun (inutile) | Oui | 17 |
4 | Python pour déclaration | Tableau Numpy | Oui | v in vs | Oui | 1640 |
5 | Python pour déclaration | Tableau Numpy | Oui | vs[i] | Oui | 1950 |
3
def square_sum_np(vs):
return np.sum(vs**2)
python
%timeit square_sum_np(vs_ndarray)
4
#Fonction définie ci-dessus
%timeit square_sum_py(vs_ndarray)
5
#Fonction définie ci-dessus
%timeit square_sum_py_range(vs_ndarray)
Alors, pensons ensuite à l'utilisation de Cython pour accélérer l'instruction for de Python. La façon de passer un tableau de bas niveau accessible en mémoire à une fonction en Cython est de spécifier le tableau Numpy directement en tant qu'argument (http://docs.cython.org/en/latest/src/tutorial/numpy). Il y a .html) et Comment spécifier une vue mémoire typée comme argument. Ici, nous allons commencer par l'ancienne méthode plus intuitive. En regardant les informations fragmentaires sur le net, il semble que seule la spécification de type soit efficace pour accélérer avec Cython. Cependant, comme vous pouvez le voir dans le tableau ci-dessous, la simple spécification des arguments de fonction dans un tableau Numpy n'est pas beaucoup plus rapide que le code Python d'origine (1) (6). À ce stade, la spécification des types d'éléments du tableau Numpy ne fait pas grand-chose (7). Bien sûr, c'est plus rapide que le code Python (4) en utilisant des tableaux Numpy, mais c'est tout, il n'y a aucune raison d'utiliser Cython.
# | syntaxe | Type d'argument | Spécification du type d'élément | Accès aux éléments | Vérifier la fonction | Temps d'exécution[ms] |
---|---|---|---|---|---|---|
6 | Cython pour instruction | Tableau Numpy | Aucun | v in vs | Oui | 378 |
7 | Cython pour instruction | Tableau Numpy | Oui | v in vs | Oui | 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)
Ensuite, ce qu'il faut faire est de changer la méthode d'accès (comment écrire l'instruction for) à l'élément de tableau. L'accès à l'index sans typage d'élément est plus lent, mais l'accès à l'index avec typage d'élément est considérablement plus rapide.
# | syntaxe | Type d'argument | Spécification du type d'élément | Accès aux éléments | Vérifier la fonction | Temps d'exécution[ms] |
---|---|---|---|---|---|---|
8 | Cython pour instruction | Tableau Numpy | Aucun | vs[i] | Oui | 1610 |
9 | Cython pour instruction | Tableau Numpy | Oui | vs[i] | Oui | 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)
La documentation officielle décrit également comment omettre la fonction de vérification pour l'accès au tableau (10-13), mais la différence entre ne pas omettre la fonction de vérification (6-9) était légère. Il est vrai que 10 à 20% est plus rapide, mais ce n'est aussi efficace que la dernière poussée.
# | syntaxe | Type d'argument | Spécification du type d'élément | Accès aux éléments | Vérifier la fonction | Temps d'exécution[ms] |
---|---|---|---|---|---|---|
10 | Cython pour instruction | Tableau Numpy | Aucun | v in vs | Aucun | 315 |
11 | Cython pour instruction | Tableau Numpy | Oui | v in vs | Aucun | 313 |
12 | Cython pour instruction | Tableau Numpy | Aucun | vs[i] | Aucun | 1610 |
13 | Cython pour instruction | Tableau Numpy | Oui | vs[i] | Aucun | 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)
Une autre façon de passer un tableau de bas niveau accessible en mémoire à la fonction Cython consiste à spécifier une vue de mémoire typée comme argument. Cette méthode est recommandée dans les documents officiels. Comme vous pouvez le voir dans les résultats ci-dessous, c'est également la méthode d'accès aux éléments du tableau qui fonctionne dans ce cas.
# | syntaxe | Type d'argument | Spécification du type d'élément | Accès aux éléments | Vérifier la fonction | Temps d'exécution[ms] |
---|---|---|---|---|---|---|
14 | Cython pour instruction | Vue mémoire typée | Oui (nécessaire) | v in vs | Oui | 519 |
15 | Cython pour instruction | Vue mémoire typée | Oui (nécessaire) | vs[i] | Oui | 26 |
16 | Cython pour instruction | Vue mémoire typée | Oui (nécessaire) | v in vs | Aucun | 516 |
17 | Cython pour instruction | Vue mémoire typée | Oui (nécessaire) | vs[i] | Aucun | 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):Même si c'est presque la même chose dans ce cas
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]: Le nombre de codes ne devient pas $ 2 \ times3 \ times2 \ times2 \ times2 = 24 $ car (1) il y a une corrélation entre les choix et (2) le code de référence est ajouté. C'est la cause. [^ 2]: Il y avait une variation maximale de ± 20% des valeurs mesurées par% timeit, mais ici nous sommes préoccupés par la différence de plusieurs à plusieurs dizaines de fois, donc je suis préoccupé par la différence de plusieurs pour cent. Non. [^ 3]: le processeur utilise Intel Celeron.
Recommended Posts