Accélérer Python avec Cython

Cython est un sur-ensemble de Python qui vous permet d'améliorer considérablement la vitesse de votre code. Vous pouvez ajouter des déclarations de type facultatives pour des avantages encore plus importants. Cython traduit votre code en C / C ++ optimisé compilé en un module d'extension Python. 

Dans ce didacticiel, vous apprendrez à installer Cython, à obtenir gratuitement une amélioration immédiate des performances de votre code Python, puis à tirer pleinement parti de Cython en ajoutant des types et en profilant votre code. Enfin, vous en apprendrez plus sur des sujets plus avancés, tels que l'intégration avec du code C / C ++ et NumPy, que vous pourrez explorer davantage pour des gains encore plus importants..

Compter les triples pythagoriciens

Pythagore était un mathématicien et philosophe grec. Il est célèbre pour son théorème de Pythagore, qui stipule que dans un triangle rectangle, la somme des carrés des jambes des triangles est égale au carré de l'hypoténuse. Les triples de Pythagore sont trois entiers positifs quelconques a, b et c tels que a² + b² = c². Voici un programme qui trouve tous les triples de Pythagore dont les membres ne sont pas plus grands que la limite fournie.

import time time def count (limite): resultat = 0 pour a dans la plage (1, limite + 1): pour b dans la plage (a + 1, limite + 1): pour c dans la plage (b + 1, limite + 1) : si c * c> a * a + b * b: pause si c * c == (a * a + b * b): résultat + = 1 résultat si __name__ == '__main__': début = heure.heure () result = count (1000) duration = time.time () - commence à imprimer (résultat, durée) Sortie: 881 13.883624076843262 

Apparemment, il y a 881 triples et il a fallu un peu moins de 14 secondes au programme pour le découvrir. Ce n'est pas trop long, mais assez long pour être ennuyeux. Si nous voulons trouver plus de triples jusqu'à une limite supérieure, nous devrions trouver un moyen de l'accélérer.. 

Il s’avère que les algorithmes sont nettement meilleurs, mais aujourd’hui, nous nous concentrons sur le fait de rendre Python plus rapide avec Cython, et non sur le meilleur algorithme permettant de rechercher des triples de Pythagore. 

Easy Boosting avec pyximport

La manière la plus simple d'utiliser Cython consiste à utiliser la fonction spéciale pyximport. Ceci est une déclaration qui compile votre code Cython à la volée et vous permet de profiter des avantages de l'optimisation native sans trop de peine. 

Vous devez mettre le code à cythoniser dans son propre module, écrire une ligne de configuration dans votre programme principal, puis l'importer comme d'habitude. Voyons à quoi ça ressemble. J'ai déplacé la fonction dans son propre fichier appelé pythagorean_triples.pyx. L'extension est importante pour Cython. La ligne qui active Cython est pyximport d'importation; pyximport.install (). Ensuite, il importe juste le module avec le compter() fonction et plus tard l'invoque dans la fonction principale.

temps d'importation importation pyximport; pyximport.install () importer pythagorean_triples def main (): start = time.time () resultat = pythagorean_triples.count (1000) duration = time.time () - lance l'impression (résultat, durée) si __name__ == '__main__': main () Sortie: 881 9.432806253433228 

La fonction pure Python a duré 50% plus longtemps. Nous avons eu ce coup de pouce en ajoutant une seule ligne. Pas mal du tout.

Construisez votre propre module d'extension

Bien que pyximport soit vraiment pratique lors du développement, il ne fonctionne que sur les modules Python purs. Souvent, lors de l'optimisation du code, vous souhaitez référencer des bibliothèques C natives ou des modules d'extension Python.. 

Pour les prendre en charge et éviter la compilation dynamique à chaque exécution, vous pouvez créer votre propre module d'extension Cython. Vous devez ajouter un petit fichier setup.py et n'oubliez pas de le construire avant d'exécuter votre programme à chaque fois que vous modifiez le code Cython. Voici le fichier setup.py:

depuis la configuration d'importation de distutils.core depuis Cython.Build, configuration d'installation de cythonize (ext_modules = cythonize ("pythagorean_triples.pyx"))

Ensuite, vous devez le construire:

$ python setup.py build_ext --inplace Compiler pythagorean_triples.pyx car il a changé. [1/1] Cythonizing pythagorean_triples.pyx exécutant l'extension 'pythagorean_triples' de build_ext building créant une construction créant une construction fwrapv -O3 -Wall -Wstrict-prototypes -I / Utilisateurs / gigi.sayfan / miniconda3 / envs / py3 / include-archive x86_64 -I / Utilisateurs / gigi.sayfan / miniconda3 / envs / py3 / include-archive x86_64 -I / Utilisateurs / gigi.sayfan / miniconda3 / envs / py3 / include / python3.6m -c pythagorean_triples.c -o build / temp.macosx-10.7-x86_64-3.6 / pythagorean_triples.o gcc -bundle-dynamique dynamique -L / Users / gigi.sayfan / miniconda3 / envs / py3 / lib-L / Utilisateurs / gigi.sayfan / miniconda3 / envs / py3 / lib-archive x86_64 build / temp.macosx-10.7-x86_64-3.6 / pythagorean_triples.o-L / Users / gigi.sayfan / miniconda3 / envs / py3 / lib -o pythagorean_triples.cpython-36m-darwin.so

Comme vous pouvez le constater à la sortie, Cython a généré un fichier C appelé pythagorean_triples.c et le compile en un fichier .so spécifique à la plate-forme, qui est le module d'extension que Python peut désormais importer comme tout autre module d'extension natif.. 

Si vous êtes curieux, jetez un coup d’œil sur le code C généré. Il est très long (2789 lignes), obtus et contient beaucoup de choses supplémentaires nécessaires pour travailler avec l'API Python. Laissons tomber le pyximport et relançons notre programme:

import time import import pythagorean_triples def main (): start = time.time () resultat = pythagorean_triples.count (1000) duration = time.time () - lance l'impression (résultat, durée) si __name__ == '__main__': main () 881 9.507064819335938 

Le résultat est quasiment identique à celui de pyximport. Cependant, notez que je ne mesure que le temps d'exécution du code cythonisé. Je ne mesure pas le temps qu'il faut à pyximport pour compiler le code cythonisé à la volée. Dans les grands programmes, cela peut être important.

Ajout de types à votre code

Passons au niveau suivant. Cython est plus que Python et ajoute un typage facultatif. Ici, je définis juste toutes les variables en tant que nombres entiers et les performances en flèche:

# pythagorean_triples.pyx def count (limite): cdef int result = 0 cdef int a = 0 cdef int b = 0 cdef int c = 0 pour a dans la plage (1, limite + 1): pour b dans la plage (a + 1 , limite + 1): pour c dans la plage (b + 1, limite + 1): si c * c> a * a + b * b: casse si c * c == (a * a + b * b): result + = 1 return result ---------- # main.py heure d'importation import pyximport; pyximport.install () importer pythagorean_triples def main (): start = time.time () resultat = pythagorean_triples.count (1000) duration = time.time () - lance l'impression (résultat, durée) si __name__ == '__main__': main () Sortie: 881 0.056414127349853516 

Oui. C'est correct. En définissant quelques entiers, le programme s'exécute en moins de 57 millisecondes, contre plus de 13 secondes avec du Python pur. C'est presque une amélioration de 250X.

Profiler votre code

J'ai utilisé le module time de Python, qui mesure le temps passé sur le mur et est assez bon la plupart du temps. Si vous souhaitez une synchronisation plus précise des fragments de code de petite taille, utilisez le module timeit. Voici comment mesurer les performances du code en utilisant timeit:

>>> import timeit >>> timeit.timeit ('count (1000)', setup = "à partir du nombre d'import pythagorean_triples", number = 1) 0.05357028398429975 # En cours d'exécution 10 fois >>> timeit.timeit ('count (1000)' , setup = "depuis le nombre d'import pythagorean_triples", nombre = 10) 0.5446877249924 

le timeit () la fonction prend une instruction à exécuter, un code d'installation qui n'est pas mesuré et le nombre d'exécutions pour exécuter le code mesuré.

Sujets avancés

Je viens de gratter la surface ici. Vous pouvez faire beaucoup plus avec Cython. Voici quelques rubriques pouvant améliorer les performances de votre code ou permettre à Cython de s'intégrer à d'autres environnements:

  • code C appelant
  • interagir avec l'API C de Python et GIL
  • utiliser C ++ en Python
  • porter du code Cython dans PyPY
  • utilisant le parallélisme
  • Cython et NumPy
  • déclarations de partage entre les modules Cython

Conclusion

Cython peut produire deux ordres de grandeur d’amélioration des performances moyennant un effort minime. Si vous développez un logiciel non trivial en Python, Cython est une évidence. Il a très peu de frais généraux, et vous pouvez l’introduire progressivement dans votre code..

De plus, n'hésitez pas à voir ce que nous avons disponible à la vente et à étudier sur le marché, et n'hésitez pas à poser des questions et à fournir vos précieux commentaires en utilisant le flux ci-dessous.