[PYTHON] Différence de vitesse d'exécution en fonction de la façon d'écrire la fonction Cython

Objectif

J'ai étudié comment la vitesse d'exécution change en fonction de la façon d'écrire des fonctions Cython.

Histoire

Contexte

Méthode

# 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

résultat

Détails

1. Préparation

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)

2. Python pour instruction

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)

3. Diffusion Numpy

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)

4. Spécification du type d'argument

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)

5. Accès à l'index

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)

6. Omission de la fonction de contrôle

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)

Vue mémoire typée

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)

Considération

Remarques

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

Différence de vitesse d'exécution en fonction de la façon d'écrire la fonction Cython
Comment écrire sobrement avec des pandas
Remarques sur la rédaction de requirements.txt
Notes sur la façon d'exécuter Cython sur OSX
Comment écrire ce processus en Perl?
Comment écrire Ruby to_s en Python
Comment écrire un test unitaire pour l'extraction d'URL dans GAE / P
Comment utiliser Python Kivy ④ ~ Exécution sur Android ~
Pour écrire dans Error Repoting en Python sur GAE
Comment écrire un document tuple nommé en 2020
[Go] Comment écrire ou appeler une fonction
Comment se moquer d'une fonction publique dans Pytest
[Linux] Différence d'informations temporelles en fonction de l'ID d'horloge de la fonction clock_gettime ()
20e Comment écrire des problèmes en temps réel hors ligne en Python
Comment écrire une concaténation de chaînes sur plusieurs lignes en Python
Comment définir Decorator et Decomaker avec une seule fonction
Comment autoriser les utilisateurs nologin à se connecter sous Linux
Différence de résultats en fonction de l'argument du multiprocessus.
Comment écrire une validation personnalisée dans Django REST Framework
Différence de sys.path en fonction du démarrage de Python (v3.8.2)
Comment utiliser VS Code dans un environnement Venv avec Windows
[Python] Comment écrire une instruction if en une phrase.
Remarques sur la façon de charger un environnement virtuel avec PyCharm
Comment échantillonner à partir de n'importe quelle fonction de densité de probabilité en Python
Résumé de l'écriture des fichiers .proto utilisés dans gRPC
Remarques sur l'utilisation de la guimauve dans la bibliothèque de schémas
[ROS] Comment écrire un éditeur et un abonné sur un nœud
Covector pour penser en fonction
XPath Basics (2) - Comment écrire XPath
Comment appeler une fonction
Comment s'inscrire auprès de pypi
Comment développer en Python
[sh] Comment stocker les résultats de l'exécution de la commande dans des variables
Comment installer OpenCV sur Cloud9 et l'exécuter en Python
Repeated @ app.callback dans Dash Comment écrire correctement l'entrée et l'état
Comment écrire du code pour accéder à python dashDB sur Bluemix ou local
Le 15e comment écrire un problème de référence en temps réel hors ligne en Python
Comment écrire en temps réel hors ligne Résolution des problèmes E04 avec Python
Comment déployer une application Django sur heroku en seulement 5 minutes
Le 14ème problème de référence d'écriture en temps réel hors ligne en python
Fonction pour ouvrir un fichier en Python3 (différence entre open et codecs.open et comparaison de vitesse)
Comment utiliser la fonction de rendu définie dans .mako (.html) directement dans mako
Le 18ème comment écrire un problème de référence en temps réel hors ligne en Python
[Python] Comment faire PCA avec Python
Comment gérer une session dans SQLAlchemy
Comment utiliser la fonction zip
Comment installer mysql-connector-python sur Mac
Comment utiliser les classes dans Theano
Comment collecter des images en Python
Comment utiliser Dataiku sous Windows
Réutilisation du flacon Comment écrire du HTML
Comment mettre à jour Spyder dans Anaconda
Comment utiliser SQLite en Python
Remarques sur l'utilisation de pywinauto
Ecrire une fonction AWS Lambda en Python
Comment installer Graph-Tool sur macOS
Comment installer VMware-Tools sur Linux
Mesurer le temps d'exécution de la fonction en Python