Cython pour optimiser Python

Cython permet de coder des extensions python compilées via un mécanisme de traduction en C. Mais il permet aussi de créer un exécutable de votre programme python. Les performances sont spectaculaires et nous allons le voir dans ce billet

Python est l'un des langages les plus aboutis de notre génération. Tant au point de vue sémantique que la souplesse qu'il permet en terme d'environnement et de possibilités. Outre les outils et modules qui permettent de travailler efficacement, des projets existent pour rendre python encore plus optimal. Beaucoup savent déjà que, comme dans pas mal de langages, il est possible de développer des extension en C pour augmenter la vitesse d'exécution d'un programme python. Mais l'écriture est un peu complexe et il souvent on préfère laisser de coté la performance en faveur de la vitesse de développement.

Python étant interprété, le programme que vous aurez créé sera alors soumi à une dépendance forte: Python lui même. Car pour exécuter un programme python, il faut python. Or comme je vous l'ai dit Python a un univers autour de lui qui permet de faire des miracles. Nous allons donc voir l'un des ses astres qui gravite autour de Python: Cython. Avec lui, vous allez pouvoir développer simplement des extension plus optimisées, et en plus pouvoir compiler un programme python en un exécutable binaire natif.

La seule dépendance sera alors la librairie python.

Voici, sous Fedora, ce que vous allez faire:

yum install Cython

Cela installe les librairies Cython + un outil de compilation qui porte le même nom. C'est parti pour l'explication.

Cython est un outil qui va permettre la traduction de votre code python en C. Un peu comme le fait shedskin. La différence est que Cython permet de générer un code en C qui sera exploitable par python. En d'autres termes, et pour être plus exact, Cython crée du code C pour générer une extenion python valide.

Prenon un simple exemple:

#file: hello.py
def printHello:
        print "Hello you !"

Ce code est simple, c'est une fonction qui, lorsqu'on l'appelle, affiche un message "Hello you !". Pour générer l'extension il existe plusieurs méthodes. La plus simple étant de faire un fichier "setup.py" (un standard python qui utilise distutils) ou utiliser Cython.

Comme je péfère expliquer les choses complexe pour les rendre simples après coup, on commence par la méthode Cython.

cython hello.py

Cela génère un fichier hello.c. Oui il est énorme, près de 1000 lignes. Le but n'est pas de faire léger mais rapide :)

Bon, on va compiler le fichier pour en faire une librairie partagée pour Python:

gcc hello.c -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing $(pkg-config python --cflags) -o hello.so

voilà donc un fichier hello.so qui est une extension python que nous pouvons utiliser:

#fichier: test.py
import hello
 
printHello()

Pour le moment, c'est pas super utile car nous ne faisons qu'une opération très simple. Mais Cython ne s'arrête pas à ça. Il permet d'utiliser des type C et même des librairies C. De plus, nous allons voir que nous pouvons faire un exécutable de notre programme. Procédons par étapes.

Passons à l'utilisation de type C, ce qui va rendre un programme plus rapide. Vous connaissez fibonacci ? voici le code qui permet de trouver le résultat de la suite à un rang donné:

#fichier fibo.py
def fibo(n):
        if n == 0 or n == 1: 
                return n
        return fibo(n-2) + fibo(n-1)

Cette fonction est particulièrement lente, d'abord parcce que nous n'avons pas optimisé le code pour garder en mémoire les suite déjà calculées, mais en plus parce que nous allons passer par Python...

Testons:

#fichier test_fibo.py
from fibo import fibo
fibo(32)

et lançons le test avec la commande "time" qui nous donnera le temps d'éxécution:

time python test_fibo.py
3524578

real    0m1.574s
user    0m1.561s
sys     0m0.008s

1,6 seconde environ... autant dire que c'est pas fameux... Voyons avec cython.

cython fibo.py
gcc fibo.c -o fibo.so -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing $(pkg-config python --cflags)

time python test_fibo.py 
3524578

real    0m1.229s
user    0m1.137s
sys     0m0.089s

Ha y'a du mieux ! 1,2 secondes, on dirait pas mais là on a gangé 30% de temps d'éxécution...

Encore mieux ? possible oui et vous allez voir, pas des moindres !

Entrons dans le vif du sujet, Cython n'est pas python mais un language à part entière. Il permet de créer des types C, je vous l'ai dit. Cela se fait avec le mot clef "cdef". Avec ce mot clef, on peut donc créer une fonction ou variable C, donc en typant et en jouant avec les retours de fonctions. Fibonacci n'utilise que des entiers. Voyons comment redéfinir notre fonction pour la rendre pure C.

En premier lieu, vous renommez fibo.py en fibo.pyx. Pourquoi ? parce que Cython a besoin de savoir que cette fois-ci nous allons utiliser un fichier prévu pour le développement C. Notre fonction va donc ressembler à cela:

#fichier fibo.pyx
cdef int fibo(int n):
        if n == 0 or n == 1: 
                return n
        return fibo(n-2) + fibo(n-1)

Souci , cette fonction ne sera pas vu par des appels externes... en d'autres termes si vous voulez appeler la fonction "fibo" depuis test_fibo.py, ça ne marchera pas. Il faut donc utiliser une des deux méthodes:

  • soit vous définissez en plus une méthode avec "def" qui apelle "fibo"
  • soit vous n'utilisez pas cdef, mais cpdef qui fera l'alias tout seul (pratique hein)

Alors la méthode 2 me parraissant plus sympa:

#fichier fibo.pyx
cpdef int fibo(int n):
        if n == 0 or n == 1: 
                return n
        return fibo(n-2) + fibo(n-1)

Allez on lance la compilation, pensez bien qu'on utilise le fichier .pyx maintenant, pas le .py:

cython fibo.pyx
gcc fibo.c -o fibo.so -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing $(pkg-config python --cflags

Et le miracle:

time python test_fibo.py 
3524578

real    0m0.070s
user    0m0.058s
sys     0m0.012s

Et oui, 70 millisecondes ! Incroyable non ?

Et si maintenant on créait un exécutable ? Soyons fou ! On va utiliser une option de Cython qui permet d'intégrer l'exécutable python dans la fonction "main", cette option s'appelle "--embed" pour "embarquer":

cython --embed test_fibo.py -o test.c

Ici j'ai demandé à sortir un fichier nommé test.c, sinon on aurait test_fibo.c et j'aime pas les underscores :)

Compilons ce source:

gcc test.c -o test $(pkg-config python --cflags) -lpython2.7

l'option "-lpython2.7" est celle qui est valable pour Fedora 14 (qui utilise python 2.7) sinon on adaptez le numéro de version à celle de votre installation. Et nous avons maintenant un fichier "test" exécutable:

./test
3524578

Il marche :) par contre on ne gagne pas grand chose en temps d'éxécution par rapport à test_fibo.py. Mais le but ici était d'avoir un exécutable qui ne demande plus que la librairie python (dans le paquet python-libs de Fedora) et non plus Python lui même.

Voyons ce que demande notre exécutable en dépendance:

ldd test
        linux-vdso.so.1 =>  (0x00007fffba369000)
        libpython2.7.so.1.0 => /usr/lib64/libpython2.7.so.1.0 (0x00007f180b44a000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f180b0ae000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f180ae92000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f180ac8e000)
        libutil.so.1 => /lib64/libutil.so.1 (0x00007f180aa8b000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f180a806000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f180b81c000)

Effectivement il demande libpython et, évidamment, fibo.so est appelé lors de l'exécution, donc pensez à avoir ce fichier dans le même répertoire ou bien placé dans le répertoire de librairies (LD_LIBRARY_PATH, ou dans les lib de python)

Voilà, c'est une "courte" introduction car en fait Cython est bien plus puissant que cela, vous aurez à loisirs d'utiliser les librairies C existante (stdlib, math...) et de rendre vos développement extrêmement puissant et rapide... Voilà encore un point qui me rend de plus en plus fan de Python.

Je vous conseille vivement d'aller voir le site de documentation de cython et notamment la page qui traite de distutils dans le tutoriel cython

Voilà voilà

Commentaires

1. Le lundi, février 21 2011, 01:06 par DiEOrLivE

Cool, super bon/beau blog! Inscris au flux x)