J'ai essayé de vérifier et d'analyser l'accélération de Python par Cython

introduction

Les langages «Python» classés comme dynamiquement typés incluent «Cython» qui convertit le code Python en C / C ++ et le compile, et «Numba» qui accélère Python à l'aide d'un compilateur JIT. Je voulais améliorer l'efficacité en les incorporant dans le code Python utilisé dans la recherche et les stages, et afin d'approfondir ma compréhension, j'ai essayé de comparer et de vérifier la vitesse de traitement pour un traitement simple réel sur jupyter. ..

Cet article n'est pas un article d'introduction sur Cython. Qu'est-ce que Cython? Si vous souhaitez connaître les connaissances de base et l'utilisation, nous vous recommandons les articles suivants.

Si vous avez des opinions ou des erreurs, n'hésitez pas à commenter. Le code expérimental est ici

Expérience 1 Comparaison avec la fonction is_prime

J'ai essayé d'augmenter la vitesse en utilisant la fonction simple ʻis_prime` (fonction de jugement principal) ci-dessous comme base. À propos du code La même partie que la ligne de base est omise. Concernant Cython, nous avons vérifié la différence de vitesse en fonction de l'emplacement du type.

baseline


def find_factor(n):
    answer = n
    for i in range (2,n):
        if n % i == 0:
            answer = i
            break
    return answer

def is_prime(n):
    if find_factor (n) != n:
        return False
    else:
        return True
Accélérez avec Numba
from numba import jit
@jit
def find_factor0 (n):
    #Abréviation
def is_prime0 (n):
    #Abréviation
Accélération avec Cython 1 (Cython1)

Compilez simplement avec Cython sans changer le code

% load_ext Cython
%% cython
def find_factor1(n):
    #Abréviation
def is_prime1(n):
    #Abréviation
Accélération avec Cython 2 (Cython2)

Tapez toutes les variables

% load_ext Cython
%% cython
def find_factor2(int n):
    cdef int answer = n
    cdef int i
    #Abréviation
def is_prime2(n):
    #Abréviation
Accélérer avec Cython 2-1 (Cython2-1)

Type-définir seulement i dans l'instruction for

% load_ext Cython
%% cython
def find_factor2_1(n):
    answer = n
    cdef int i
    #Abréviation
def is_prime2_1(n):
    #Abréviation
Accélération avec Cython 2-2 (Cython2-2)

Tapez uniquement la réponse

% load_ext Cython
%% cython
def find_factor2_2(n):
    cdef int answer = n
    #Abréviation
def is_prime2_2(n):
    #Abréviation
Accélérer avec Cython 2-3 (Cython2-3)

Définition de type uniquement argument n

% load_ext Cython
%% cython
def find_factor2_3(int n):
    #Abréviation
def is_prime2_3(int n):
    #Abréviation
Accélération avec Cython 2-4 (Cython2-4)

Définition de type autre que l'argument n

% load_ext Cython
%% cython
def find_factor2_4(n):
    cdef int answer = n
    cdef int i
    #Abréviation
def is_prime2_4(n):
    #Abréviation
Accélération avec Cython 3 (Cython3)

Définissez la fonction findfactor avec cdef

% load_ext Cython
%% cython
cdef int find_factor3(int n):
    cdef int answer = n
    cdef int i
    #Abréviation
def is_prime3(n):
    #Abréviation

Dans toutes les expériences, le temps nécessaire pour déterminer le nombre premier 131071 a été mesuré par "% timeit". Le temps moyen et l'écart type de chaque processus exécuté 100 fois sont les suivants.

Comparaison de la vitesse de la fonction is_prime
Baseline Numba Cython1
Aucun changement de code
Cython2
La fonction elle-même
Cython3
Toutes les variables de la fonction
temps(ms) 8.69±0.2 0.49±0.0 5.34±0.1 0.43±0.0 0.43±0.0
Comparaison de la vitesse par définition de type en Cython
Cython2-1
pour la déclaration i seulement
Cython2-2
réponse seulement
Cython2-3
Argument n uniquement
Cython2-4
Autre que l'argument n
temps(ms) 4.81±0.1 5.27±0.4 6.62±0.1 4.85±0.1

D'après le résultat de Cython2, c'est le plus rapide pour Cython lorsque toutes les informations de type sont définies. Considérons maintenant le changement de vitesse dû à la différence dans la définition du type. Comme on peut le voir à partir de la différence entre Cython2-1 et Cython2-2, l'effet des variables de définition de type est largement dû à la première (variable d'itérateur de définition de type ʻi). Cela est dû au fait que le calcul d'incrément effectué dans la boucle for a un contrôle de type pour chaque opération (équivalent au processus d'appel de ʻint .__ add__ à chaque fois), donc s'il est beaucoup exécuté, la surcharge sera élevée, mais la définition de type On pense que cette surcharge est éliminée en calculant ʻi + 1` directement sur le langage C.

De plus, en faisant attention à Cython2-3, la définition de type avec uniquement des arguments est plus lente que Cython1. Je pensais que cela devrait être plus rapide en supprimant la vérification de type dynamique dans les arguments lors de l'appel de la fonction find factor, mais c'était en fait plus lent. Considérant qu'il y a une surcharge de compilation due à la définition du type d'argument plutôt qu'à ces effets, nous avons défini les fonctions suivantes hoge1, hoge2, qui ne font que différer uniquement par la présence ou l'absence de la définition du type d'argument, et comparé les vitesses. Cependant, il y avait une légère différence.

%% cython
def hoge1 (n,a,b,c):
    return
def hoge2 (int n, int a, int b, int c):
    return

Cependant, même avec la fonction ci-dessus, la différence était d'environ 10 ns (10 millions de boucles sont tournées et la différence ne devrait pas être une erreur par rapport à la valeur de l'écart type), et en plus, il n'y avait presque pas de différence lorsqu'il y avait un argument, donc cette surcharge On ne considère pas que la différence est due à. Vous pouvez également voir à partir de Cython2-4 qu'il est plus rapide de taper les arguments si toutes les variables du find factor sont tapées. D'après ce qui précède, la cause du retard en Cython2-3 est la dynamique lorsqu'une seule des opérations «n% i == 0» est tapée que lorsque ni «n ni i» ne sont tapés. Je pense que la conclusion est que les frais généraux du chèque sont plus importants, mais je n'ai pas compris la raison.

Le résultat de la saisie du find factor lui-même à partir de Cython3 n'était pas très différent de Cython2. Le traitement arithmétique lui-même dans find factor est le même que Cython2, donc je suis convaincu. Cependant, en Cython3, la surcharge lors de l'appel de find factor devrait être supprimée, donc appeler find factor une fois ne fait presque aucune différence, mais je pense que Cython3 sera plus rapide dans le processus d'appels de beaucoup. J'ai créé la fonction suivante et l'ai vérifiée.

%% cython

cdef int find_factor3_1(int n):
    #Abréviation
def find_factor3_2(int n):
    #Abréviation

def is_prime_inf1(n,m):
    for i in range (m):
        find_factor3_1 (n)

def is_prime_inf2(n,m):
    for i in range (m):
        find_factor3_2 (n)
Comparaison lorsque le facteur de recherche est appelé 10000 fois
find factor3-1(cdef) find factor3-2(def)
temps 158 μs 3.1s

Une différence significative est apparue en appelant un grand nombre de «facteurs de recherche» comme prévu. Par conséquent, il a été constaté que la surcharge de l'appel en définissant le «facteur de recherche» lui-même est éliminée.

Expérience 2 Comparaison par produit matriciel

J'ai essayé d'augmenter la vitesse avec le produit matriciel comme base de référence.

Implémenté par Python
def dot(a, b):
    n = len(a)
    l = len(a[0])
    m = len(b[0])
    c = np.zeros((n, m))
    for i in range(n):
        for j in range(m):
            for k in range(l):
                c[i][j] += a[i][k] * b[k][j]
    return c
Implémentation par Numba
from numba import jit
@jit
def nm_dot(a, b):
    #Abréviation
Implémentation par Cython 1 (Cython1)

Pour le moment, la définition de type est effectuée sur la base des connaissances à ce jour et implémentée avec cython. Changez en [i] [j][i, j] pour accélérer, déclarez les définitions de type ensemble, changez en range xrange, désactivez la fonction de vérification, etc. Nous concevons.

%%cython
import numpy as np
cimport numpy as np

cython: boundscheck=False
cython: wraparound=False
    
cpdef np.ndarray  c_dot(np.ndarray a, np.ndarray b):
    cdef np.ndarray c
    cdef int n,l,m,i,j,k
    n = len(a)
    l = len(a[0])
    m = len(b[0])
    c = np.zeros((n, m))
    for i in xrange(n):
        for j in xrange(m):
            for k in xrange(l):
                c[i,j] += a[i,k] * b[k,j]
    return c
Implémentation par Cython 2 (Cython2)

La définition de type pour le tableau numpy a été rendue plus rigoureuse en référence à la documentation officielle.

cpdef np.ndarray[dtype=float,ndim = 2]  c_dot2(np.ndarray[dtype=float,ndim = 2] a, np.ndarray[dtype=float,ndim = 2] b):
    cdef np.ndarray[dtype=double,ndim = 2] c
    #Abréviation

Dans cette expérience, une matrice Numpy aléatoire de 100 * 100 a été générée et mesurée avec % timeit comme dans l'expérience 1. La moyenne et les écarts-types pour 10 processus dans l'implémentation chronophage de Python et Cython 1 et 100 processus dans les autres cas sont les suivants.

Comparaison des vitesses des produits matriciels
Python Python(Numpy) Numba Cython1 Cython2
temps(ms) 1220±54 0.033 1.1 729±16 2.5

C'est un peu plus rapide en Cython1 que l'implémentation naïve de Python, mais beaucoup plus rapide en Cython2. Dans l'implémentation en Cython1, lorsque le type de chaque tableau numpy est défini comme np.ndarray, le dtype et la dimension réels ne sont pas encore déterminés et il est soumis à une vérification dynamique après tout, ce qui équivaut au traitement lorsque Cython n'est pas utilisé. On pense que c'est parce qu'il est stocké. Comme vous pouvez le voir à partir des résultats, Numpy a été extrêmement rapide lorsqu'il s'agissait de calculer le produit matriciel. En premier lieu, j'ai beaucoup utilisé Numpy, mais je ne connaissais pas le principe pourquoi c'était si rapide, alors j'ai fait un peu de recherche (j'aurais dû le faire en premier). , Il a été dit qu'il a été implémenté en langage C en interne. Ici aussi, la frappe est sortie et j'ai réalisé l'importance de la frappe. Il est préférable d'utiliser numpy tel quel, au moins pour les calculs implémentés en tant que modules dans numpy tels que les produits matriciels, et Cython lorsqu'il n'est pas implémenté en tant que module numpy et ne peut pas être réalisé sans accéder réellement au tableau numpy plusieurs fois Cela semble bon à utiliser.

en conclusion

Grâce à cette expérience, l'introduction pratique de Cython ne tape pas dans les nuages sombres, mais améliore efficacement les performances tout en considérant les avantages de Python tels que la flexibilité et la lisibilité du code, et l'utilisation d'excellentes bibliothèques telles que Numpy. J'ai senti qu'il était bon d'identifier les endroits où cela serait possible.

Matériel de référence

Recommended Posts

J'ai essayé de vérifier et d'analyser l'accélération de Python par Cython
J'ai essayé de vérifier la classification yin et yang des membres hololive par apprentissage automatique
J'ai essayé de vérifier le résultat du test A / B avec le test du chi carré
J'ai essayé d'analyser la carte du Nouvel An par moi-même en utilisant python
J'ai essayé de vérifier l'identification du locuteur par l'API de reconnaissance du locuteur d'Azure Cognitive Services avec Python. # 1
J'ai essayé de vérifier l'identification du locuteur par l'API de reconnaissance du locuteur d'Azure Cognitive Services avec Python. # 2
J'ai essayé de résumer les opérations de chaîne de Python
J'ai essayé d'obtenir et d'analyser les données statistiques de la nouvelle Corona avec Python: données de l'Université John's Hopkins
J'ai essayé de trouver l'entropie de l'image avec python
[Python] J'ai essayé de visualiser la relation de suivi de Twitter
Je veux connaître la nature de Python et pip
J'ai essayé d'énumérer les différences entre java et python
J'ai essayé d'automatiser la mise à jour de l'article du blog Livedoor avec Python et sélénium.
J'ai essayé de comparer la vitesse de traitement avec dplyr de R et pandas de Python
[Introduction à Python] J'ai comparé les conventions de nommage de C # et Python.
J'ai essayé d'améliorer l'efficacité du travail quotidien avec Python
J'ai essayé Web Scraping pour analyser les paroles.
J'ai essayé de déplacer l'image vers le dossier spécifié en faisant un clic droit et un clic gauche
[Python] J'ai essayé d'analyser le lanceur qui n'a réussi aucun coup, aucune course
J'ai essayé de corriger la forme trapézoïdale de l'image
J'ai essayé d'utiliser le module Datetime de Python
J'ai essayé de trouver l'itinéraire optimal du pays des rêves par recuit (quantique)
J'ai essayé d'extraire et d'illustrer l'étape de l'histoire à l'aide de COTOHA
J'ai essayé de résumer le contenu de chaque paquet enregistré par Python pip en une seule ligne
Je veux analyser les sentiments des gens qui veulent se rencontrer et trembler
J'ai essayé d'analyser la négativité de Nono Morikubo. [Comparer avec Posipa]
Qiita Job J'ai essayé d'analyser le travail
J'ai essayé de rationaliser le rôle standard des nouveaux employés avec Python
[Linux] J'ai essayé de vérifier la méthode de confirmation sécurisée du FQDN (CentOS7)
J'ai essayé d'obtenir les informations sur le film de l'API TMDb avec Python
J'ai essayé de vectoriser les paroles de Hinatazaka 46!
Django super introduction par les débutants Python! Partie 2 J'ai essayé d'utiliser les fonctions pratiques du modèle
J'ai essayé de livrer du courrier depuis Node.js et Python en utilisant le service de livraison de courrier (SendGrid) d'IBM Cloud!
J'ai essayé de notifier la mise à jour de "Hameln" en utilisant "Beautiful Soup" et "IFTTT"
[Python] J'ai essayé de juger l'image du membre du groupe d'idols en utilisant Keras
J'ai essayé de visualiser facilement les tweets de JAWS DAYS 2017 avec Python + ELK
J'ai essayé d'automatiser le dépôt de 100 yens des courses de chevaux Rakuten (python / sélénium)
J'ai essayé de prédire la présence ou l'absence de neige par apprentissage automatique.
J'ai essayé de récupérer les données de l'ordinateur portable en le démarrant sur Ubuntu
J'ai essayé de refactoriser le code de Python débutant (lycéen)
J'ai essayé de passer le test G et la qualification E en m'entraînant à partir de 50
J'ai essayé d'envoyer automatiquement la littérature du nouveau virus corona à LINE avec Python
J'ai essayé de représenter graphiquement les packages installés en Python
J'ai essayé de résumer comment utiliser matplotlib de python
J'ai essayé de résumer la forme de base de GPLVM
J'ai essayé de toucher un fichier CSV avec Python
J'ai essayé de résoudre Soma Cube avec python
J'ai vérifié les versions de Blender et Python
[Python] J'ai essayé de représenter graphiquement le top 10 des ombres à paupières
J'ai essayé d'effacer la partie négative de Meros
J'ai essayé de résoudre le problème avec Python Vol.1
[Python] J'ai essayé d'obtenir Json de squid ring 2
J'ai essayé de classer les voix des acteurs de la voix
[Python & SQLite] J'ai analysé la valeur attendue d'une course avec des chevaux dans la fourchette 1x win ①