Quelles sont les problématiques d'interfaçage, les avantages/inconvénients des langages, la stratégie de code, etc. dans le développement d'un serveur métier ?
Comment développer un serveur métier en python/C++
1. Un serveur métier en Python / C++
Meet-up C++
02/10/2014
Pierre Marquis & Alexandre Bonnasseau
2. Plan
• Retour d’expérience
• En pratique, quelques lignes de code
3. L'équipe dev carto
Info
trafic
Itinéraire
Plan
Moteur de
recherche
géolocalisée
Moteur de
suggestion
Géolocalisation
Itinéraire
transports
en commun
10. Aujourd’hui
+ 80% des serveurs migrés
+ Langage très intéressant
+ Framework mieux
maîtrisé
+ Dev plus rapide
+ Plus d’agilité
- Moins de maîtrise C++
- Multithread
- Débogage binding
12. Cadre
– Existence d’une base de code C++ fiable et testée
– On souhaite prototyper rapidement (besoin mouvant)
– Besoin d’exécution rapide pour des fonctions critiques
14. Objectif
Utiliser les fonctions C++ en python :
$ python
Python 2.7.3
>>> import geo
>>> print geo.geocoord2string(5.36)
5° 21’ 36‘’
Projet complet disponible sur github :
https://github.com/Mappy/un-serveur-metier-en-python-cpp
15. Un module python écrit en C++
D'après : https://docs.python.org/2/extending/extending.html
geomodule.cpp
#include "Python.h"
#include "geo.hpp"
static PyObject * geocoord2string_py(PyObject *self, PyObject *args)
{
double angle = 0;
if (!PyArg_ParseTuple(args, "d", &angle))
return NULL;
string res = geocoord2string(angle);
return Py_BuildValue("s", res.c_str());
}
// La liste des fonctions qu'on expose en Python
static PyMethodDef geoMethods[] = {
{ "geocoord2string", geocoord2string_py, METH_VARARGS,
"Convert a latitude or a longitude as an angle in a string in the form : d° m' s''." },
{ NULL, NULL, 0, NULL } /* Sentinel */
};
// La fonction initnomdumodule est appelée par l'interpréteur Python
// à l'import du module
PyMODINIT_FUNC initgeo(void)
{
(void) Py_InitModule("geo", geoMethods);
}
16. Avec Boost.Python ?
geomudule.cpp
#include <boost/python.hpp>
#include <boost/python/module.hpp>
#include "geo.hpp"
using namespace boost::python;
BOOST_PYTHON_MODULE(geo)
{
def("geocoord2string", geocoord2string);
class_<GeoPoint>("GeoPoint", init<double, double>())
.def("distance", &GeoPoint::distance)
… C’est tout !
;
}
17. Avec Boost.Python, on peut
– Exposer des fonctions C++ en Python
– Exposer des classes C++ en Python
– Choisir quelles méthodes exposer pour une classe
– Utiliser Boost::optional pour gérer les paramètres
optionnels
18. Packager une extension Python
Le fichier setup.py s’occupe de la compilation :
from distutils.core import setup, Extension
geo_module = Extension('geo',
include_dirs = ['/usr/local/include',],
library_dirs = ['/usr/local/lib',],
extra_compile_args=['-std=c++11'],
sources = ['geomodule.cpp', 'geo.cpp'])
setup (name = 'Geo',
version = '1.0',
description = 'A geo package from Mappy',
author = 'LBS team',
author_email = 'lbs@mappy.com',
url = 'http://github.com/mappy',
long_description = ‘A demo package with geo functions',
ext_modules = [geo_module])
Installer avec :
python setup.py install
Ou packager :
python setup.py bdist
19. Focus : Gestion de la mémoire
– Un code C++ qui conserve une référence sur
un objet Python doit appeler la macro
Py_INCREF() pour s'assurer que l'objet ne
sera pas détruit
– Il faut appeler Py_DECREF() pour libérer
l'objet
20. Focus : Gestion des erreurs
Le code suivant lève une exception C++ :
import geo
print geo.geocoord2string(181)
Sans Boost.Python :
terminate called after throwing an instance of 'std::invalid_argument'
what(): Invalid argument : angle must be beetween -180° and 180°
Avec Boost.Python :
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: Invalid argument : angle must be beetween -180° and 180°
Boost.Python permet également d'affiner la gestion des erreurs en associant des
erreurs spécifiques Python aux exceptions C++
21. Un serveur de distance géographiques
geoserveur.py
from bottle import route, run, template
from geo import GeoPoint
@route('/distance/<lat1:float>,<lng1:float>/<lat2:float>,<lng2:float>')
def index(lat1, lng1, lat2, lng2):
dep = GeoPoint(lat1, lng1)
arr = GeoPoint(lat2, lng2)
return template("Distance = {{distance}}", distance=dep.distance(arr))
run(host='localhost', port=8888)
A lancer par :
python geoserver.py
Tester l’url : http://localhost:8888/distance/48.85,2.35/43.30,5.38
22. Une autre approche : Producteur / Consommateur
– Le code python peut appeler des méthodes C++ à
distance
– Par exemple via un bus RabbitMQ ou bien des
appels distants ZMQ
– Au final, le code Python délègue l‘exécution des
sections critiques à des workers C++
24. Typage dynamique
– En C++ le typage statique offre une première
validation du code
– En Python, on ne détecte les erreurs de type qu'à
l'exécution !
il faut exécuter le code pour le tester
les tests unitaires sont indispensables !
25. Threads
– Le Global Interpreter Lock (GIL) assure que les traitements
concurrents ne se marchent pas dessus :
https://docs.python.org/2/glossary.html#term-global-interpreter-lock
http://dabeaz.blogspot.fr/2010/01/python-gil-visualized.html
– Les modules thread / threading
– encapsulent les primitives système de thread
– fournissent des primitives de synchronisation : verrous,
sémaphores, etc.
– ... Mais à aucun moment deux instructions peuvent être
exécutées en même temps à cause du GIL !
Mieux vaut utiliser multiprocessing
26. Mémoire partagée
– Le module mmap (Memory-mapped file) permet de partager de
la mémoire entre plusieurs process
– On peut accéder à cette mémoire comme un fichier en lecture /
écriture
– On accède à cette mémoire comme un tableau de char
– Mais on ne peut pas utiliser de types complexes (listes,
dictionnaires, objets définis sur mesures)
Pas aussi souple qu’en C++
28. Où va Mappy ?
– Amélioration continue de l’outillage
– Nouveaux services asynchrones en Python
– … Et bientôt un nouveau serveur d'itinéraire en Python / C++ ?