Qu'est-ce que Cython? Python à la vitesse de C

Python a la réputation d'être l'un des langages de programmation les plus pratiques, les mieux équipés et les plus utiles. Vitesse d'exécution? Pas tellement.

Entrez Cython. Le langage Cython est un sur-ensemble de Python qui se compile en C, produisant des améliorations de performances pouvant aller de quelques pour cent à plusieurs ordres de grandeur, en fonction de la tâche à accomplir. Pour le travail lié aux types d'objets natifs de Python, les accélérations ne seront pas importantes. Mais pour les opérations numériques, ou toute opération n'impliquant pas les propres composants internes de Python, les gains peuvent être énormes. 

Avec Cython, vous pouvez contourner de nombreuses limitations natives de Python ou les transcender entièrement, sans avoir à renoncer à la facilité et à la commodité de Python. Dans cet article, nous allons parcourir les concepts de base de Cython et créer une application Python simple qui utilise Cython pour accélérer l'une de ses fonctions.

Vidéo connexe: Utiliser Cython pour accélérer Python

Compilez Python en C

Le code Python peut effectuer des appels directement dans les modules C. Ces modules C peuvent être des bibliothèques C génériques ou des bibliothèques spécialement conçues pour fonctionner avec Python. Cython génère le deuxième type de module: les bibliothèques C qui parlent aux internes de Python et qui peuvent être regroupées avec du code Python existant.

Le code Cython ressemble beaucoup au code Python, de par sa conception. Si vous alimentez le compilateur Cython avec un programme Python (Python 2.x et Python 3.x sont tous deux pris en charge), Cython l'acceptera tel quel, mais aucune des accélérations natives de Cython n'entrera en jeu. Mais si vous décorez le code Python avec des annotations de type dans la syntaxe spéciale de Cython, Cython pourra remplacer des équivalents C rapides par des objets Python lents.

Notez que l'approche de Cython est  incrémentielle . Cela signifie qu'un développeur peut commencer avec une  application Python existante et l'accélérer en apportant des modifications ponctuelles au code, plutôt que de réécrire l'ensemble de l'application à partir de zéro.

Cette approche correspond à la nature des problèmes de performances logicielles en général. Dans la plupart des programmes, la grande majorité du code gourmand en CPU est concentrée dans quelques points chauds - une version du principe de Pareto, également connue sous le nom de règle «80/20». Ainsi, la plupart du code d'une application Python n'a pas besoin d'être optimisé en termes de performances, juste quelques éléments critiques. Vous pouvez traduire progressivement ces points chauds en Cython, et ainsi obtenir les gains de performances dont vous avez besoin là où cela compte le plus. Le reste du programme peut rester en Python pour la commodité des développeurs.

Comment utiliser Cython

Considérez le code suivant, tiré de la documentation de Cython:

def f (x):

    retour x ** 2-x

def integrer_f (a, b, N):

    s = 0

    dx = (ba) / N

    pour i dans la plage (N):

        s + = f (a + i * dx)

    retourne s * dx

Ceci est un exemple de jouet, une implémentation pas très efficace d'une fonction intégrale. En tant que code Python pur, il est lent, car Python doit convertir entre les types numériques natifs de la machine et ses propres types d'objets internes.

Considérons maintenant la version Cython du même code, avec les ajouts de Cython soulignés:

 cdef double f (double x):

    retour x ** 2-x

def integrer_f (double a, double b, int N):

    cdef int i

    cdef double s, x, dx

    s = 0

    dx = (ba) / N

    pour i dans la plage (N):

        s + = f (a + i * dx)

    retourne s * dx

Si nous déclarons explicitement les types de variables, tant pour les paramètres de la fonction et les variables utilisées dans le corps de la fonction ( double, int, etc.), Cython traduira tout cela en C. On peut aussi utiliser le cdefmot - clé pour définir les fonctions qui sont implémentées principalement en C pour plus de vitesse, bien que ces fonctions ne puissent être appelées que par d'autres fonctions Cython et non par des scripts Python. (Dans l'exemple ci-dessus, ne integrate_fpeut être appelé que par un autre script Python.)

Notez à quel point notre code actuel  a peu changé. Tout ce que nous avons fait, c'est d'ajouter des déclarations de type au code existant pour obtenir une amélioration significative des performances.

Avantages de Cython

En plus de pouvoir accélérer le code que vous avez déjà écrit, Cython offre plusieurs autres avantages:

Travailler avec des bibliothèques C externes peut être plus rapide

Les packages Python tels que NumPy enveloppent les bibliothèques C dans des interfaces Python pour les rendre faciles à utiliser. Cependant, les allers-retours entre Python et C via ces wrappers peuvent ralentir les choses. Cython vous permet de parler directement aux bibliothèques sous-jacentes, sans Python. (Les bibliothèques C ++ sont également prises en charge.)

Vous pouvez utiliser la gestion de la mémoire C et Python

Si vous utilisez des objets Python, ils sont gérés en mémoire et récupérés de la même manière que dans Python classique. Mais si vous voulez créer et gérer vos propres structures de niveau C et utiliser malloc/ freetravailler avec elles, vous pouvez le faire. N'oubliez pas de nettoyer après vous-même.

Vous pouvez opter pour la sécurité ou la vitesse au besoin 

Cython effectue automatiquement des vérifications à l'exécution pour les problèmes courants qui surgissent en C, tels que l'accès hors limites sur un tableau, via des décorateurs et des directives de compilation (par exemple, @boundscheck(False)). Par conséquent, le code C généré par Cython est beaucoup plus sûr par défaut que le code C roulé manuellement, bien que potentiellement au détriment des performances brutes.

Si vous êtes convaincu que vous n'aurez pas besoin de ces vérifications au moment de l'exécution, vous pouvez les désactiver pour des gains de vitesse supplémentaires, soit sur l'ensemble d'un module, soit uniquement sur certaines fonctions.

Cython vous permet également d'accéder nativement aux structures Python qui utilisent le protocole buffer pour un accès direct aux données stockées en mémoire (sans copie intermédiaire). Les vues de mémoire de Cython vous permettent de travailler avec ces structures à grande vitesse et avec le niveau de sécurité approprié à la tâche. Par exemple, les données brutes sous-jacentes à une chaîne Python peuvent être lues de cette façon (rapide) sans avoir à passer par le runtime Python (lent).

Le code Cython C peut bénéficier de la publication du GIL

Global Interpreter Lock de Python, ou GIL, synchronise les threads dans l'interpréteur, protégeant l'accès aux objets Python et gérant les conflits pour les ressources. Mais le GIL a été largement critiqué comme un obstacle à un Python plus performant, en particulier sur les systèmes multicœurs.

Si vous avez une section de code qui ne fait aucune référence aux objets Python et effectue une opération de longue durée, vous pouvez la marquer avec la  with nogil:directive pour lui permettre de s'exécuter sans le GIL. Cela libère l'interpréteur Python pour faire d'autres choses et permet au code Cython d'utiliser plusieurs cœurs (avec un travail supplémentaire).

Cython peut utiliser la syntaxe d'indication de type Python 

Python a une syntaxe d'indication de type qui est principalement utilisée par les linters et les vérificateurs de code, plutôt que par l'interpréteur CPython. Cython a sa propre syntaxe personnalisée pour les décorations de code, mais avec les récentes révisions de Cython, vous pouvez utiliser la syntaxe d'indication de type Python pour fournir également des indications de type de base à Cython. 

Cython peut être utilisé pour masquer du code Python sensible

Les modules Python sont très faciles à décompiler et à inspecter, mais les binaires compilés ne le sont pas. Lorsque vous distribuez une application Python aux utilisateurs finaux, si vous souhaitez protéger certains de ses modules de la surveillance occasionnelle, vous pouvez le faire en les compilant avec Cython. Notez, cependant, qu'il s'agit d'un effet secondaire des capacités de Cython, pas de l'une de ses fonctions prévues.

Limitations de Cython

Gardez à l'esprit que Cython n'est pas une baguette magique. Il ne transforme pas automatiquement chaque instance de code Python poky en code C ultra-rapide. Pour tirer le meilleur parti de Cython, vous devez l'utiliser à bon escient et comprendre ses limites:

Peu d'accélération pour le code Python conventionnel

Lorsque Cython rencontre du code Python qu'il ne peut pas traduire complètement en C, il transforme ce code en une série d'appels C aux composants internes de Python. Cela revient à sortir l'interpréteur de Python de la boucle d'exécution, ce qui donne au code une vitesse modeste de 15 à 20% par défaut. Notez qu'il s'agit d'un meilleur scénario; dans certaines situations, vous pourriez ne constater aucune amélioration des performances, voire une dégradation des performances.

Peu d'accélération pour les structures de données natives Python

Python fournit une multitude de structures de données: chaînes, listes, tuples, dictionnaires, etc. Ils sont extrêmement pratiques pour les développeurs et disposent de leur propre gestion automatique de la mémoire. Mais ils sont plus lents que le pur C.

Cython vous permet de continuer à utiliser toutes les structures de données Python, bien que sans grande accélération. C'est, encore une fois, parce que Cython appelle simplement les API C dans le runtime Python qui créent et manipulent ces objets. Ainsi, les structures de données Python se comportent généralement comme du code Python optimisé pour Cython: vous obtenez parfois un coup de pouce, mais seulement un peu. Pour de meilleurs résultats, utilisez des variables et des structures C. La bonne nouvelle est que Cython facilite leur travail.

Le code Cython s'exécute plus rapidement en "pur C"

Si vous avez une fonction en C étiquetée avec le cdefmot - clé, avec toutes ses variables et appels de fonction en ligne à d'autres choses qui sont du C pur, elle fonctionnera aussi vite que C peut aller. Mais si cette fonction fait référence à un code natif Python, comme une structure de données Python ou un appel à une API Python interne, cet appel sera un goulot d'étranglement des performances.

Heureusement, Cython fournit un moyen de repérer ces goulots d'étranglement: un rapport de code source qui montre en un coup d'œil quelles parties de votre application Cython sont du C pur et quelles parties interagissent avec Python. Plus l'application est optimisée, moins il y aura d'interaction avec Python.

Cython NumPy 

Cython améliore l'utilisation des bibliothèques tierces de calcul des nombres basées sur C telles que NumPy. Étant donné que le code Cython se compile en C, il peut interagir directement avec ces bibliothèques et éliminer les goulots d'étranglement de Python de la boucle.

Mais NumPy, en particulier, fonctionne bien avec Cython. Cython prend en charge nativement des constructions spécifiques dans NumPy et fournit un accès rapide aux tableaux NumPy. Et la même syntaxe NumPy familière que vous utiliseriez dans un script Python conventionnel peut être utilisée en Cython tel quel.

Cependant, si vous souhaitez créer les liaisons les plus proches possibles entre Cython et NumPy, vous devez décorer davantage le code avec la syntaxe personnalisée de Cython. L'  cimportinstruction, par exemple, permet au code Cython de voir les constructions de niveau C dans les bibliothèques au moment de la compilation pour les liaisons les plus rapides possibles.

Étant donné que NumPy est si largement utilisé, Cython prend en charge NumPy «prêt à l'emploi». Si NumPy est installé, vous pouvez simplement indiquer  cimport numpy votre code, puis ajouter une décoration supplémentaire pour utiliser les fonctions exposées. 

Profilage et performances Cython

Vous obtenez les meilleures performances de n'importe quel morceau de code en le profilant et en voyant de première main où se trouvent les goulots d'étranglement. Cython fournit des hooks pour le module cProfile de Python, vous pouvez donc utiliser les propres outils de profilage de Python, comme cProfile, pour voir comment votre code Cython fonctionne. 

Il est utile de se rappeler dans tous les cas que Cython n'est pas magique - que des pratiques de performance sensées dans le monde réel s'appliquent toujours. Moins vous faites la navette entre Python et Cython, plus votre application s'exécutera rapidement.

Par exemple, si vous avez une collection d'objets que vous souhaitez traiter en Cython, ne la parcourez pas en Python et appelez une fonction Cython à chaque étape. Passez toute la collection à votre module Cython et effectuez une itération là-bas. Cette technique est souvent utilisée dans les bibliothèques qui gèrent des données, c'est donc un bon modèle à émuler dans votre propre code.

Nous utilisons Python car il offre une commodité au programmeur et permet un développement rapide. Parfois, cette productivité du programmeur se fait au détriment des performances. Avec Cython, un petit effort supplémentaire peut vous donner le meilleur des deux mondes.

En savoir plus sur Python

  • Qu'est-ce que Python? Programmation puissante et intuitive
  • Qu'est-ce que PyPy? Python plus rapide sans douleur
  • Qu'est-ce que Cython? Python à la vitesse de C
  • Tutoriel Cython: Comment accélérer Python
  • Comment installer Python de manière intelligente
  • Les meilleures nouvelles fonctionnalités de Python 3.8
  • Meilleure gestion de projet Python avec Poetry
  • Virtualenv et venv: les environnements virtuels Python expliqués
  • Python Virtualenv et Venv à faire et à ne pas faire
  • Explication des threads et sous-processus Python
  • Comment utiliser le débogueur Python
  • Comment utiliser timeit pour profiler le code Python
  • Comment utiliser cProfile pour profiler le code Python
  • Démarrez avec async en Python
  • Comment utiliser asyncio en Python
  • Comment convertir Python en JavaScript (et inversement)
  • Python 2 EOL: Comment survivre à la fin de Python 2
  • 12 pythons pour chaque besoin de programmation
  • 24 bibliothèques Python pour chaque développeur Python
  • 7 IDE Python que vous avez peut-être manqués
  • 3 lacunes majeures de Python et leurs solutions
  • 13 frameworks Web Python comparés
  • 4 frameworks de test Python pour écraser vos bugs
  • 6 nouvelles fonctionnalités Python à ne pas manquer
  • 5 distributions Python pour maîtriser l'apprentissage automatique
  • 8 grandes bibliothèques Python pour le traitement du langage naturel