Présentation au Meet-up C++ le 2 octobre 2014 à Paris : http://www.meetup.com/User-Group-Cpp-Francophone/events/181945092/
Par Alexandre Bonnasseau de Mappy
Base de code C++ fiable et testée
On souhaite prototyper rapidement (besoin mouvant)
Temps d'execution rapide pour des fonctions critiques
Base de code
geo.cpp
#define _USE_MATH_DEFINES
#include <cmath>
#include "geo.hpp"
#include <sstream>
string geocoord2string(double angle) {
if ( (angle > 180) || (angle < -180) )
throw invalid_argument("Invalid argument : angle must be beetween -180° and 180°");
int deg = int(floor(angle));
double rest = angle - deg;
int minute = int(floor( rest * 60));
rest = rest * 60 - minute;
int second = int(floor( rest * 60));
ostringstream result;
result << deg << "° " << minute << "' " << second <<"''";
return result.str();
}
double deg2rad(double deg) {
return (deg * M_PI / 180);
}
GeoPoint::GeoPoint(double lat, double lng) : lat(lat), lng(lng)
{}
double GeoPoint::distance(const GeoPoint &other) {
// Haversine distance : http://en.wikipedia.org/wiki/Haversine_formula
double nDLat = deg2rad(other.lat - this->lat);
double nDLon = deg2rad(other.lng - this->lng);
double thisLatRad = deg2rad(this->lat);
double otherLatRad = deg2rad(other.lat);
double nA = pow ( sin(nDLat/2), 2 ) + cos(thisLatRad) * cos(otherLatRad) * pow ( sin(nDLon/2), 2 );
double nC = 2 * atan2( sqrt(nA), sqrt( 1 - nA ));
double nD = EARTH_RADIUS * nC;
return nD;
}
D'après : https://docs.python.org/2/extending/extending.html
On écrit une fonction compréhensible par Python :
#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,
"Conver 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'interpreteur Python
// à l'import du module
PyMODINIT_FUNC initgeo(void)
{
(void) Py_InitModule("geo", geoMethods);
}
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',
ext_modules = [geo_module])
Installer avec python setup.py install
ou python setup.py bdist
#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)
;
}
- Exposer des fonctions C++ en Python
- Définir des classes
- Choisir quelles méthodes exposer en Python pour une classe C++
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
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 al getion des erreurs en associant des erreurs spécifiques Python à des exceptions C++
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 tester avec :
http://localhost:8888/distance/48.85,2.35/43.30,5.38
Si le code C++ concentre les routines critiques en temps d'exécution :
Le code python peut appeler des méthodes C++ à distance
Par exemple via un bus RabbitMQ ou bien des appels ZMQ
Au final, le code Python délègue l'éxécution des sections critiques à des workers C++
- 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 executer le code pour le tester
- => les tests unitaires sont indispensables !
Le Global Interpreter Lock(GIL) assure que les traitements concurents ne se marchent pas dessus
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 executées en même temps à cause du GIL !
Mieux vaut utiliser multiprocessing
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
Continuer à développer l'outillage et les sections de code non critiques en Python Bientôt refaire le serveur d'itinéraire en Python / C++ : - graphe des voies en Europe : 20 Go - 50 requetes / secondes - répondre en 300 ms