Entries for category C++

Wed Sep 15 2010

C++, MongoDB, Programming

asio, boost, bson, c++, mongodb, mongoxx, proto

9 comments

mongoxx, driver C++ alternatif pour MongoDB

J'utilise MongoDB pour une poignée de projets mais je n'ai jamais eu besoin des différents drivers python, php ou encore javascript. Aucun, sauf le driver C++. Bon, OK, si on considère que le shell mongo est un driver javascript, alors oui, je l'utilise beaucoup[1].

Le driver C++ est intimement lié au projet MongoDB dans la mesure où il est maintenu par les core developers -une partie du staff de 10gen- et qu'il partage la plupart de ses sources avec celles du server.

Il y a des avantages à ce qu'un tel driver soit à ce point corrélé au server. Premièrement, il fait partie du cercle fermé des drivers officiels, ceux développés par 10gen. Deuxièmement, des améliorations au niveau du server peuvent être répercutées sur le driver puisque les sources sont communes (par exemple les mécanismes de réplication). Enfin, comme la plupart des tests unitaires et fonctionnels du projet s'appuient sur ce driver, nul besoins de préciser que ses bugs sont corrigés en priorité.

Maintenant, les désavantages, car il y en a.

Compiler le driver, c'est à dire obtenir libmongoclient et une série de fichiers headers n'est pas si simple que ça. Le build system de MongoDB est basé sur SCons, un software construction tool écrit en python. SCons est un très bon outil. Un projet est construit (compilé, installé etc) à l'aide d'instructions python présentes dans un fichier SConstruct ainsi que, optionnellement, autant de fichiers SConscript qu'il y a de sous-répertoires et sous-projets.

MongoDB est une collection de projets. Le server principal mongod, mais aussi des tools tels que mongosniff, mongodump, le shell mongo, des exécutables pour les tests ou le driver C++ qui est, comme nous l'avons vu, une librairie qui se compile et est pleinement intégrée au source tree principal.

MongoDB n'a recours qu'à un seul et unique fichier SConstruct pour compiler et installer tous les sous-projets pour toutes les plateformes (diverses distribs Linux, Solaris, Windows, 32bit, 64bits etc). Le fichier a grossi exponentiellement. Celui-ci est régulièrement tweaké au gré des besoins imposés par un rythme de développement soutenu.

Cela n'est pas gênant dans la mesure où la méthode conseillée pour installer MongoDB est de télécharger des packages prêts à l'emploi pour une plateforme donnée. Nul besoin de lancer ni même installer SCons. Comme la plupart des applications utilisant MongoDB sont écrites en ruby, python ou php[2] et que leurs drivers respectifs sont développés sur des canaux séparés, la majorité des utilisateurs n'a que faire des subtilités du build system.

Seul les core devs, les buildbots et les utilisateurs du driver C++ (et bien sûr les hackers en tout genre;) sont amenés à s'adapter. C'est mon cas. Depuis le début, je maintiens un fork qui me permet de m'accomoder en douceur, si je puis dire.

Pendant un temps, j'ai soumis quelques patches en fonctions des problèmes que je rencontrais. Par exemple le fait d'utiliser spidermonkey en provenance du dépot mercurial de mozilla-central plutôt que des tarballs individuels ou des packages (j'ai depuis switché pour V8). Ou encore le fait d'utiliser diverses versions de GCC avec différents flags.

Le driver C++ a toujours eu un statut un peu bâtard, principalement parce que personne ne l'utilise à part le projet MongoDB, ou presque. Récemment, les choses se sont dégradées et à moins de bidouiller le build system, il n'est plus possible d'effectuer une installation conforme dans un répertoire cible contenant les includes et lib nécessaires, et donc ne pas pénaliser les projets qui dépendent d'une telle installation.

Grâce à mon fork, ça va, je gère.. pour l'instant. Ce n'est pas le cas de tout le monde. Les gars de 10gen ont prévu de normaliser la situation. Comme ils savent un peu (légèrement;) mieux que moi ce qu'il faut faire pour opérer un découplage propre sans briser tout le reste, je prends mon mal en patience.

...jusqu'à il y a quelques jours. Après tout, quel intérêt ai-je à dépendre de ce driver, qui même correctement compilé et installé, n'est pas dénué de défauts comme nous allons le voir.

Quand j'ai commencé à écrire mes premiers codes utilisant ce driver, ce qui m'a le plus étonné et frustré était à quel point les développeurs ont été peu précautionneux au regard des pratiques élémentaires d'importation des namespaces C++.

En gros, le moindre include de header mongo avait pour conséquence d'importer tous les symbols de la librairie standard, mais aussi quelques namespaces de la librairie Boost, dans le namespace global. C'est simple, Il était impossible d'utiliser le driver C++ pour un projet autre que MongoDB lui-même. Heureusement, un fix d'antologie -par votre serviteur- a permis de limiter les dégâts à moindre frais ;) Maintenant, c'est le namespace mongo qui est pollué. Un soin curatif complet demanderait une qualification complète de tous les noms dans tous les fichiers headers et des importations sur demande dans les fichiers sources. Une tâche longue et fastidieuse que personne n'a eu le courage d'entreprendre.

mongoxx

Du coup j'ai entrepris d'écrire mon propre driver C++, mongoxx, entièrement découplé des sources de MongoDB. L'idée, c'est que ce soit découplé, d'accord, mais surtout explorer certaines librairies Boost que je n'ai jamais encore utilisé. Je pense à Proto ou Move dont la review est pending. Pour la partie communication, Asio bien entendu.

Quid de BSON, le format "JSON binaire" omniprésent dans MongoDB? Je n'ai pas trouvé de container suffisamment souple avec lequel travailler pour implémenter les specs BSON. Un std::vector<char> ne fait pas complètement l'affaire, pas plus qu'un boost::array.

Du coup je me suis monté un container binaire générique qui repose ultimement sur des malloc/realloc et que j'utilise pour implémenter BSON mais aussi pour construire les messages envoyés au server qui contiennent souvent des docs BSON en plus de headers/flags spécifiques.

Mes premiers essais avec Proto sont vraiment fantastiques ;) Cette librairie est une tuerie. Un petit teaser:

// 'doc_', 'genoid', 'undefined', 'now' sont des terminals proto.
// l'espace mémoire requis est calculé à la compilation
// si possible (pod types) 
 
doc d = doc_
    ("_id", genoid)
    ("machin", "ok")
    ("truc", undefined)
    ("embed", doc_
        ("val", 3.14)
        ("date", now))
;
 
d << "last" << 42; // grows as needed
 
// STL iterator ready
std::for_each(d.begin(), d.end(), [](elem const& e){
    pretty_print(e);
});

Le but c'est que l'interface du driver soit simple et à peu près conforme aux conventions de la librairie standard ainsi que Boost, ce que le driver C++ de MongoDB n'est pas du tout. Par exemple être compatible avec les itérateurs pour parcourir des documents BSON ou des results sets et donc pouvoir y appliquer les algorithms standards.

Actuellement j'ai un chantier de proofs of concept que je veux résorber et structurer, en commençant par y ajouter des tests unitaires avant d'aller plus loin. Je push ensuite tout ça sur github. Comme je souhaite me débarrasser complètement de libmongoclient, je ne vais pas vous cacher que j'ai encore pas mal de boulot ;)

[1] Une bonne partie de l'administration de ce site se fait à partir d'un shell mongo.

[2] MongoDB survey results publié le 18 Fev 2010.

Thu Apr 08 2010

C++, NoteToMyself, Programming

boost, c++, phoenix

0 comment

Boost Phoenix 2.0 tips

Quelques tips pour Boost.Phoenix2 glanés dans la sueur et dans le sang (dans la doc aussi).

Higher higher order function

On a vite fait d'imbriquer les algorithms de Phoenix, mais il ne faut pas oublier qu'ils sont déjà des higher order functions. Leur prédicats doivent être évalués de manière lazy et même lazy lazy si je puis dire.

Un code vaut mieux qu'un long schema à main levée. Dans ce qui suit, j'ai deux std::vector de int. Je veux afficher les éléments du premier vector qui ne sont pas dans le second. Un code à la con qui a juste valeur d'exemple. Dedans y est glissée une erreur grossière. Saurez-vous la trouver ? Votre compilo saura la trouver lui, et il se fera un plaisir de générer environ 50ko de message d'erreur :)

#include <iostream>
#include <vector>
#include <algorithm>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/home/phoenix/scope/local_variable.hpp>
#include <boost/spirit/home/phoenix/statement/if.hpp>
#include <boost/spirit/home/phoenix/scope/let.hpp>
#include <boost/spirit/home/phoenix/container.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_algorithm.hpp>

int main()
{
    using boost::phoenix::arg_names::arg1;
    using boost::phoenix::local_names::_a;
    using boost::phoenix::ref;
    using boost::phoenix::let;
    using boost::phoenix::if_;
    using boost::phoenix::find_if;
    using boost::phoenix::end;
	
    std::vector<int> a;
    std::vector<int> b;
	
    a.push_back(1);
    a.push_back(2);
    a.push_back(3);
	
    b.push_back(2);
	
    std::for_each(a.begin(), a.end(),
        let(_a = arg1)
        [
            if_(end(ref(b)) == find_if(ref(b), arg1 == _a))
            [
                std::cout << _a << std::endl
            ]
        ]
    );
	
    return 0;
}

Le problème est que find_if attend un prédicat, dont on pourrait penser qu'il est tout à fait correcte en tant que composite phoenix arg1 == _a. Mais le bloc du find_if est déjà un composite, autrement dit une higher order function. Son prédicat doit être évalué de manière lazy lazy et non pas lazy -tout court.

La solution est fournie par phoenix: le composite lambda va permettre de transformer le prédicat récalcitrant en une higher higher order function. Le tour est joué:

std::for_each(a.begin(), a.end(),
    let(_a = arg1)
    [
        // lambda[arg1 == _a]
        if_(end(ref(b)) == find_if(ref(b), lambda[arg1 == _a]))
        [
            std::cout << _a << std::endl
        ]
    ]
);

Bind to overloaded member function

Qui n'a jamais été tenté de binder à gogo avec phoenix, tellement c'est facile ? Le prob survient le jour où on tente désespérément de binder une fonction qui a des overloads. le compilo se plaint avec un message du genre bind(<unresolved overloaded function type>, ..25 lignes de types phoenix..).

Example. Je cherche à binder std::map<K,V>::find. On va prendre int pour K et V. m est une instance quelconque de ce type map:

typedef std::map<int, int> map_t;

bind(&map_t::find, ref(m), arg1);

Pas bon. map_t::find a plusieurs overloads. Le compilo ne sait pas lequel choisir. Il faut le guider, après tout, c'est nous les boss dans l'histoire!

Méthode one liner:

bind(static_cast<
        map_t::const_iterator (map_t::*)(int const&) const
     >(&map_t::find), ref(m), arg1);

On utilise ce bon vieux static_cast.

Autre méthode, passer par un pointer sur fonction membre:


map_t::const_iterator (map_t::*find_f)(int const&) const = &map_t::find;

bind(find_f, ref(m), arg1);

Ca fait son job. On n'en demande pas plus.

Conclusion

RAS

Wed Apr 07 2010

C++

c++, standard

2 comments

Un point sur les C++0x Core Language Features

Le Visual C++ Team Blog vient de publier une table listant les features de C++0x C++11 qui seront disponibles dans Visual Studio 2010.

Le site de GCC met à disposition un document similaire pour son compilateur. Le wiki d'Apache propose également un tableau mettant en concurrence divers compilos.

Les variadic templates (pdf) ne seront donc pas supportées dans VS2010. C'est fort dommage, car elles font partie des features incontournables, et j'oserai même dire emblématiques, du prochain standard C++.

Naïf, peut être, candide, surement, gros noob (aussi), j'avais espéré que Microsoft finisse par les implémenter à temps pour la release de leur produit. Après tout, si on regarde la liste, VS2010 fournira une bonne part des grosses nouveautés, celles qui sont vraiment catchy, à commencer par les lambda expressions (pdf), les rvalue references ou encore, le must, les right angle brackets ;)

Fri Mar 19 2010

C++, Programming

boost, c++, json, r8t, spirit, template, v8, variant

2 comments

r8t, un moteur de template pour C++ à base de javascript

L'idée de base, mais vraiment de base, consiste à clamer à qui veut bien l'entendre que JSON est un format idéal pour passer des données à un système de Vue, comprendre View, dans un MVC. Ce format a tout ce qu'il faut: simple, compact, expressif et connu de tous, ou presque.

Le constat de base, mais vraiment de base (oui vous savez), c'est qu'il y a très peu de systèmes de template pour C++ qui ont un penchant web, c'est à dire principalement orientés génération de HTML. Je ne sais pas pourquoi, peut être parce que tout le monde s'en fout ? ;) Toujours est-il que lorsque je me suis mis en recherche d'un tel système de template, pour des besoins perso, je n'ai rien trouvé d'autre que Clearsilver et google-ctemplate. Il me fallait un truc standalone et léger. J'aurai pu chercher un peu plus, mais je me suis arrêté là, pas vraiment convaincu.

C'est là que je me suis souvenu de l'idée de base: JSON. En fait ça tombait bien puisque j'utilisais déjà le moteur javascript V8 dans mon projet. Cet engine, initialement conçu par google pour le navigateur Chrome, est open source et son API est en C++. L'idée qui a commencé à émerger est la suivante:

  • On balance du "JSON" au système de template
  • La logique de présentation est controllée par javascript
  • Le système crache du text en retour (principalement du HTML)

Sympa. Reste plus qu'à implémenter ça.

Le JSON, expédié avec une technique à base de Boost.Variant récursif que j'ai présenté dans ce billet Simple modélisation de JSON en C++.

Le javascript qui controle la logique de présentation est embarqué dans les templates sous une forme proche d'un mix de Django et de la syntaxe alternative de php.

{% for (i in posts) : %}
  <div class="post">
    <h2>{%= posts[i].title %}</h2>
    ...
  </div>
{% end %}

Le principe c'est que tout ce qui est entre {% ... %} est grosso modo du javascript qui sera inchangé avant de le donner au moteur js. Une phase de parsing transforme le fichier de template (ou n'importe quel text) en une forme intermédiaire qui sera consumée par V8. Pour l'exemple ci-dessus, cela donne quelque chose proche de ceci:

for (i in posts) {__pr('  <div class="post">\n    <h2>');
__p(posts[i].title);__pr('</h2>\n    ...\n  </div>\n');};

Le parser est écrit avec la librairie Boost.Spirit Classic. __p() pour "print" et __pr() pour "print raw" sont des fonctions javascript dont l'implémentation est en C++ et qui permettent de controller la sortie textuelle finale, par exemple en appliquant des filtres d'échappement automatique pour du HTML. Ces filtres sont inspirés des modifiers de google-ctemplate. Du genre {%:h:s= comment %} pour un filtre h qui échappe du HTML et un autre s qui permet de wrapper des snippets entre des éléments <pre>.

Ce qui est sympathique c'est qu'il est possible de transformer des sources javascript textuelles en un byte code propre à V8. C'est dans l'API. On peut donc mettre en cache des templates pre compilées, un peu comme le fait APC pour php, mais ça dépote encore plus! J'avais effectué quelques benchmarks qui laissaient penser que ce moteur de template à base de V8 était plus véloce que php+APC. Il faudrait que je mesure ça plus sérieusement.

A vrai dire ce projet de système de template a été codé à l'arrache courant novembre 2009. Un simple proof of concept à l'origine. Les dernières librairies de Boost notamment Spirit 2 (ça tue!), Phoenix 2 et Fusion 2 (OK, beaucoup de 2) m'ont donné envie de réécrire un tel système. J'héberge le truc sur github. Je lui ai donné le joli nom de r8t, réunion de rat et V8. Pour l'instant, c'est succinct. Pourquoi rat? Parce que ;)

older »