Tutoriel Cython: Comment accélérer Python

Python est un langage de programmation puissant, facile à apprendre et à utiliser, mais il n'est pas toujours le plus rapide à exécuter, en particulier lorsqu'il s'agit de mathématiques ou de statistiques. Les bibliothèques tierces comme NumPy, qui encapsulent les bibliothèques C, peuvent améliorer considérablement les performances de certaines opérations, mais parfois, vous avez juste besoin de la vitesse et de la puissance brutes de C directement dans Python.

Cython a été développé pour faciliter l'écriture d'extensions C pour Python et pour permettre au code Python existant d'être transformé en C. De plus, Cython permet au code optimisé d'être livré avec une application Python sans dépendances externes.

Dans ce didacticiel, nous allons parcourir les étapes nécessaires pour transformer le code Python existant en Cython et l'utiliser dans une application de production.

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

Un exemple Cython

Commençons par un exemple simple tiré de la documentation de Cython, une implémentation pas très efficace d'une fonction intégrale:

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

Le code est facile à lire et à comprendre, mais il s'exécute lentement. En effet, Python doit constamment effectuer des conversions entre ses propres types d'objets et les types numériques bruts de la machine.

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

 cdef 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

Ces ajouts nous permettent de déclarer explicitement des types de variables dans tout le code, afin que le compilateur Cython puisse traduire ces ajouts «décorés» en C. 

Vidéo connexe: Comment Python facilite la programmation

Parfait pour l'informatique, Python simplifie de nombreux types de travail, de l'automatisation du système au travail dans des domaines de pointe comme l'apprentissage automatique.

Syntaxe Cython

Les mots-clés utilisés pour décorer le code Cython ne se trouvent pas dans la syntaxe Python conventionnelle. Ils ont été développés spécifiquement pour Cython, donc tout code décoré avec eux ne fonctionnera pas comme un programme Python conventionnel.

Voici les éléments les plus courants de la syntaxe de Cython:

Types de variables

Certains des types de variables utilisés dans Cython sont des échos de propres types de Python, tels que  int, floatet long. D'autres types de variables Cython se trouvent également en C, comme charou struct, tout comme des déclarations comme unsigned long. Et d'autres sont uniques à Cython, comme bintune représentation au niveau C des True/Falsevaleurs Python .

Les types de fonction cdefetcpdef

Le cdefmot-clé indique l'utilisation d'un type Cython ou C. Il est également utilisé pour définir des fonctions comme vous le feriez en Python.

Les fonctions écrites en Cython à l'aide du defmot - clé de Python sont visibles par d'autres codes Python, mais entraînent une pénalité de performances. Les fonctions qui utilisent le cdefmot-clé ne sont visibles que pour les autres codes Cython ou C, mais s'exécutent beaucoup plus rapidement. Si vous avez des fonctions qui ne sont appelées qu'en interne à partir d'un module Cython, utilisez cdef.

Un troisième mot clé, cpdefassure la compatibilité avec le code Python et le code C, de telle sorte que le code C puisse accéder à la fonction déclarée à pleine vitesse. Cette commodité a un coût, cependant: les  cpdeffonctions génèrent plus de code et ont un peu plus de surcharge d'appel que cdef.

Autres mots-clés Cython

D'autres mots-clés en Cython permettent de contrôler les aspects du déroulement et du comportement du programme qui ne sont pas disponibles en Python:

  • gilet nogil. Ce sont des gestionnaires de contexte utilisés pour délimiter les sections de code qui nécessitent ( with gil:) ou ne nécessitent pas ( with nogil:) Python's Global Interpreter Lock, ou GIL. Le code C qui n'effectue aucun appel à l'API Python peut s'exécuter plus rapidement dans un nogilbloc, surtout s'il effectue une opération de longue durée telle que la lecture à partir d'une connexion réseau.
  • cimportCela demande à Cython d'importer des types de données, des fonctions, des variables et des types d'extensions C. Les applications Cython qui utilisent les modules C natifs de NumPy, par exemple, utilisent cimportpour accéder à ces fonctions.
  • include. Cela place le code source d'un fichier Cython dans un autre, à peu près de la même manière que dans C. Notez que Cython a un moyen plus sophistiqué de partager des déclarations entre des fichiers Cython autres que juste includes.
  • ctypedef. Utilisé pour faire référence aux définitions de type dans les fichiers d'en-tête C externes.
  • extern. Utilisé avec cdefpour faire référence aux fonctions ou variables C trouvées dans d'autres modules.
  • public/api. Utilisé pour faire des déclarations dans les modules Cython qui seront visibles par d'autres codes C.
  • inline. Utilisé pour indiquer qu'une fonction donnée doit être en ligne, ou avoir son code placé dans le corps de la fonction appelante chaque fois qu'elle est utilisée, pour des raisons de vitesse. Par exemple, la ffonction dans l'exemple de code ci-dessus pourrait être décorée avec inlinepour réduire sa surcharge d'appel de fonction, car elle n'est utilisée qu'à un seul endroit. (Notez que le compilateur C peut effectuer automatiquement sa propre insertion, mais inlinevous permet de spécifier explicitement si quelque chose doit être inséré.)

It is not necessary to know all of the Cython keywords in advance. Cython code tends to be written incrementally—first you write valid Python code, then you add Cython decoration to speed it up. Thus you can pick up Cython’s extended keyword syntax piecemeal, as you need it.

Compile Cython

Now that we have some idea of what a simple Cython program looks like and why it looks the way it does, let’s walk through the steps needed to compile Cython into a working binary.

To build a working Cython program, we will need three things:

  1. The Python interpreter. Use the most recent release version, if you can.
  2. The Cython package. You can add Cython to Python by way of the pip package manager: pip install cython
  3. A C compiler.

Item #3 can be tricky if you’re using Microsoft Windows as your development platform. Unlike Linux, Windows doesn’t come with a C compiler as a standard component. To address this, grab a copy of Microsoft Visual Studio Community Edition, which includes Microsoft’s C compiler and costs nothing. 

Note that, as of this writing, the most recent release version of Cython is 0.29.16, but a beta version of Cython 3.0 is available for use. If you use pip install cython, the most current non-beta version will be installed. If you want to try out the beta, use pip install cython>=3.0a1 to install the most recent edition of the Cython 3.0 branch. Cython’s developers recommend trying the Cython 3.0 branch whenever possible, because in some cases it generates significantly faster code.

Cython programs use the .pyx file extension. In a new directory, create a file named num.pyx that contains the Cython code example shown above (the second code sample under “A Cython example”) and a file named main.py that contains the following code:

from num import integrate_f

print (integrate_f(1.0, 10.0, 2000))

This is a regular Python progam that will call the integrate_f function found in num.pyx. Python code “sees” Cython code as just another module, so you don’t need to do anything special other than import the compiled module and run its functions.

Finally, add a file named setup.py with the following code:

from distutils.core import setup from distutils.extension import Extension from Cython.Build import cythonize ext_modules = [ Extension( r'num', [r'num.pyx'] ), ] setup( name="num", ext_modules=cythonize(ext_modules),

)

setup.py is normally used by Python to install the module it’s associated with, and can also be used to direct Python to compile C extensions for that module. Here we’re using setup.py to compile Cython code.

If you’re on Linux, and you have a C compiler installed (typically the case), you can compile the .pyx file to C by running the command: 

python setup.py build_ext --inplace

If you’re using Microsoft Windows and Microsoft Visual Studio 2017 or better, you’ll need to make sure you have the most recent version of setuptools installed in Python (version 46.1.3 as of this writing) before that command will work. This ensures that Python’s build tools will be able to auto-detect and use the version of Visual Studio you have installed.

If the compilation is successful, you should see new files appear in the directory: num.c (the C file generated by Cython) and a file with either a .o extension (on Linux) or a .pyd extension (on Windows). That’s the binary that the C file has been compiled into. You may also see a \build subdirectory, which contains the artifacts from the build process.

Run python main.py, and you should see something like the following returned as a response:

283.297530375

That’s the output from the compiled integral function, as invoked by our pure Python code. Try playing with the parameters passed to the function in main.py to see how the output changes.

Note that whenever you make changes to the .pyx file, you will need to recompile it. (Any changes you make to conventional Python code will take effect immediately.)

The resulting compiled file has no dependencies except the version of Python it was compiled for, and so can be bundled into a binary wheel. Note that if you refer to other libraries in your code, like NumPy (see below), you will need to provide those as part of the application’s requirements.

How to use Cython

Now that you know how to “Cythonize” a piece of code, the next step is to determine how your Python application can benefit from Cython. Where exactly should you apply it?

For best results, use Cython to optimize these kinds of Python functions:

  1. Functions that run in tight loops, or require long amounts of processing time in a single “hot spot” of code.
  2. Functions that perform numerical manipulations.
  3. Functions that work with objects that can be represented in pure C, such as basic numerical types, arrays, or structures, rather than Python object types like lists, dictionaries, or tuples.

Python has traditionally been less efficient at loops and numerical manipulations than other, non-interpreted languages. The more you decorate your code to indicate it should use base numerical types that can be turned into C, the faster it will do number-crunching.

Using Python object types in Cython isn’t itself a problem. Cython functions that use Python objects will still compile, and Python objects may be preferable when performance isn’t the top consideration. But any code that makes use of Python objects will be limited by the performance of the Python runtime, as Cython will generate code to directly address Python’s APIs and ABIs.

Another worthy target of Cython optimization is Python code that interacts directly with a C library. You can skip the Python “wrapper” code and interface with the libraries directly.

However, Cython does not automatically generate the proper call interfaces for those libraries. You will need to have Cython refer to the function signatures in the library’s header files, by way of a cdef extern from declaration. Note that if you don’t have the header files, Cython is forgiving enough to let you declare external function signatures that approximate the original headers. But use the originals whenever possible to be safe.

One external C library that Cython can use right out of the box is NumPy. To take advantage of Cython’s fast access to NumPy arrays, use cimport numpy (optionally with as np to keep its namespace distinct), and then use cdef statements to declare NumPy variables, such as cdef np.array or np.ndarray.

Cython profiling

The first step to improving an application’s performance is to profile it—to generate a detailed report of where the time is being spent during execution. Python provides built-in mechanisms for generating code profiles. Cython not only hooks into those mechanisms but has profiling tools of its own.

Python’s own profiler, cProfile, generates reports that show which functions take up the most amount of time in a given Python program. By default, Cython code doesn’t show up in those reports, but you can enable profiling on Cython code by inserting a compiler directive at the top of the .pyx file with functions you want to include in the profiling:

# cython: profile=True

You can also enable line-by-line tracing on the C code generated by Cython, but this imposes a lot of overhead, and so is turned off by default.

Note that profiling imposes a performance hit, so be sure to toggle profiling off for code that is being shipped into production.

Cython can also generate code reports that indicate how much of a given .pyx file is being converted to C, and how much of it remains Python code. To see this in action, edit the setup.py file in our example and add the following two lines at the top:

import Cython.Compiler.Options

Cython.Compiler.Options.annotate = True

(Alternatively, you can use a directive in setup.py to enable annotations, but the above method is often easier to work with.)

Supprimez les .cfichiers générés dans le projet et réexécutez le setup.pyscript pour tout recompiler. Lorsque vous avez terminé, vous devriez voir un fichier HTML dans le même répertoire que partage le nom de votre fichier dans .pyx ce cas,  num.html. Ouvrez le fichier HTML et vous verrez les parties de votre code qui dépendent toujours de Python surlignées en jaune. Vous pouvez cliquer sur les zones jaunes pour voir le code C sous-jacent généré par Cython.