En utilisant C++, faire un exemple simple permettant de comparer la mise en oeuvre et les performances de:
La fonction choisie pour l'exemple: le PGCD: plus grand commun diviseur de 2 nombres entiers.
Suggestion: si vous voulez construire les 3 exemples ci-dessous et en faire des comparaisons de performances, il faut placer chacun d'eux dans un répertoire séparé, par exemple pgcd1, pgcd2 et pgcd3, et placer un fichier “__init__.py” vide (signature du “package” Python) à la racine de chacun d'eux:
essaicython (répertoire) test.py (code de test des 3 exemples) pgcd1 (répertoire) __init__.py ... (code de l'exemple 1) pgcd2 (répertoire) __init__.py ... (code de l'exemple 2) pgcd3 (répertoire) __init__.py ... (code de l'exemple 3)
Cela vous permettra de tester les 3 exemples dans un programme test, dans lequel vous pourrez importer les 3 pgcd comme suit:
from pgcd1.pgcd1 import pgcd1 from pgcd2.pgcd2 import pgcd2 from pgcd3.pgcd3 import pgcd3
Attention sous Windows: choix du compilateur
Python prendra le compilateur par défaut. Si vous avez Visual Studio de Microsoft (comme moi), c'est lui qui sera utilisé (ça fonctionne très bien!). Mais si vous voulez MinGW au lieu de Visual Studio, il y a 2 choses à faire:
1- créer un fichier texte “setup.cfg” placé au même niveau que setup.py, et contenant:
[build] compiler = mingw32
2- corriger le fichier C:\Python27\Lib\distutils\cygwinccompiler.py pour retirer toute mention de l'option “-mno-cygwin” si elle génère une erreur.
Moyennant quoi, chez moi, ça marche avec les 2 compilateurs.
Module pgcd1.py:
#!/usr/bin/python # -*- coding: utf-8 -*- from __future__ import division # Python 2.7 def pgcd1(a,b): """Plus Grand Commun Diviseur entre les 2 nombres entiers a et b""" while b<>0: a,b=b,a%b return a
Il est difficile de faire plus simple!
Fonction en Cython pgcd2.pyx:
#!/usr/bin/python # -*- coding: utf-8 -*- from __future__ import division # Python 2.7 # definition des types Python from cpython cimport bool, list, dict, tuple, long as llong # definition des type d'objets from cython import cclass, ccall, cfunc, returns as creturns, locals as clocals ############################################################################# @ccall @creturns(int) @clocals(a=int, b=int) def pgcd2(a,b): """Plus Grand Commun Diviseur entre les 2 nombres entiers a et b""" while b<>0: a,b=b,a%b return a
A noter que j'utilise une syntaxe peu utilisée mais très pratique: toutes les définitions nécessaires à Cython pour compiler sont ajoutées sous forme de décorateurs. Cela permet de garder quasi intactes les fonctions en pur Python.
Pour le traitement Cython, on fabrique le setup.py suivant:
from distutils.core import setup from distutils.extension import Extension from Cython.Distutils import build_ext ext_modules = [Extension("pgcd2", sources=["pgcd2.pyx"], include_dirs=["."], language="c++" ) ] setup( name = 'PGCD2', ext_modules = ext_modules, cmdclass = {'build_ext': build_ext}, )
Vous voyez que c'est dans le setup qu'on désigne le C++ comme choix du compilateur.
Il ne reste plus qu'à exécuter le traitement par (à exécuter dans le répertoire du setup):
python setup.py build_ext --inplace
Petit complément par rapport au cas précédent: la fonction pgcd écrite pour Cython va en fait appeler une fonction pgcd écrite en C++ et renvoyer sa valeur. Il s'agit d'un simple exemple qui permettra au code Cython de faire appel à du code C++ directement.
Voilà déjà le code C++ choisi à titre d'exemple:
bib.cpp:
#include "bib.h" int pgcd_(int a, int b){ int r; while (b!=0) { r = a%b; a = b; b = r; } return a; }
Et l'en-tête bib.h:
int pgcd_(int a, int b);
Voici le code destiné à Cython pgcd3.pyx:
#!/usr/bin/python # -*- coding: utf-8 -*- from __future__ import division # Python 2.7 # definition des types Python from cpython cimport bool, list, dict, tuple, long as llong # definition des type d'objets from cython import cclass, ccall, cfunc, returns as creturns, locals as clocals # reference a un objet defini en C++ cdef extern from "bib.h": cdef int pgcd_(int a, int b) ############################################################################# @ccall @creturns(int) @clocals(a=int, b=int) def pgcd3(a, b): """PGCD de a et de b""" return pgcd_(a,b)
Et voici le setup.py:
from distutils.core import setup from distutils.extension import Extension from Cython.Distutils import build_ext ext_modules = [Extension("pgcd3", sources=["pgcd3.pyx", "bib.cpp"], include_dirs=["."], language="c++" ) ] setup( name = 'PGCD3', ext_modules = ext_modules, cmdclass = {'build_ext': build_ext}, )
Vous voyez que le fichier en code C++ est dans la liste des sources.
Pour exécuter la compilation, on fera dans la console et dans le répertoire du setup:
python setup.py build_ext --inplace
Voilà le code qui permettra de tester les 3 exemples:
#!/usr/bin/python # -*- coding: utf-8 -*- from __future__ import division # Python 2.7 from pgcd1.pgcd1 import pgcd1 from pgcd2.pgcd2 import pgcd2 from pgcd3.pgcd3 import pgcd3 from timeit import default_timer from random import randint n = 1000000 a = randint(10,10000) b = randint(10,10000) t1 = default_timer() for c in xrange(0, n): pgcd1(a, b) t1 = default_timer()-t1 print t1 t2 = default_timer() for c in xrange(0, n): pgcd2(a, b) t2 = default_timer()-t2 print t2 t3 = default_timer() for c in xrange(0, n): pgcd3(a, b) t3 = default_timer()-t3 print t3 print t2/t1, t3/t2
Exemple d'affichage:
1.51095759895 0.330764389199 0.295863063216 0.218910437612 0.894482818819
Constatations:
Conclusion: la solution “Cython” est vraiment intéressante!
Attention cependant: lorsqu'on utilise dans Cython des variables de même type que C++, c'est très rapide, mais si on veut utiliser des variables Python qui n'existent pas en C++, ça se gâte un peu, parce qu'on oblige le C++ à faire de nombreux appels à l'API Python. A la limite, on peut obtenir un temps d'exécution plus long qu'en pur Python (ça m'est déjà arrivé)!
Amusez-vous bien!