Ce soir, j’ai décidé d’être paresseux, et comme beaucoup de blogueurs, je vous sers ici un peu de précuit, un peu de réchauffé : une liste de trucs et astuces !
Mais pas n’importe laquelle ! Récemment, j’ai twitté sur le triste état des ressources disponibles sur le Web français apparaissant dans des recherches telles que « généricité C ». Il y a beaucoup trop de cours et autres tutos superficiels, destinés à inculquer aux débutants quelques bases du langage ou de la programmation. J’ai donc décidé de réagir, à mon échelle, en publiant sur mon modeste blog des articles sur le C pour les bons ! :-° Voici donc le premier : le préprocesseur C, pour les bons !
Je ne saurais être tenu responsable de l’utilisation que vous faites des techniques décrites ici. En particulier, si vous vous faites insulter pour code illisible ou quelque chose comme cela, il ne faudra pas venir vous plaindre. :]
Si en lisant ce qui suit, vous ne vous sentez pas très à l’aise, ou ne comprenez pas quelque chose, il vous manque peut-être quelques notions de base ; mais pas de panique, il vous suffit d’aller lire un cours quelconque, comme par exemple ce tuto dédié au préprocesseur, sur le SdZ.
La plupart des astuces que je présente ici sont illustrées dans des
frameworks tels que COS. En moins violent, et plus usité, les
en-têtes BSD `sys/queue.h` et `sys/tree.h`, dont
je reparlerai probablement bientôt, sont également de bonnes
illustrations de certaines techniques présentées ci-dessous. Citons
aussi SGLIB, dans la même veine que sys/queue.h et sys/tree.h,
mais poussant le concept un peu plus loin.
J’ai classé les astuces par ordre croissant de degré d’aliénation requis pour accepter de les utiliser. :-° Prêts ? Let’s rock!
,, ?:, && et ||
On vous a toujours dit que l’opérateur ,, c’était mal, que c’était
Le Mal, après ‘goto‘. Mais il y a un cas où
celui-ci peut s’avérer être un allié… intéressant. Il s’agit du
contexte des macros.
Bien souvent, vous aurez envie que votre macro se comporte le plus possible comme une fonction, c’est-à-dire que si celle-ci renvoie une valeur, vous pouvez l’utiliser dans une expression.
C’est là que l’opérateur virgule (,) entre en jeu : il vous permet
de placer plusieurs expressions dans votre macro, et que le tout
soit réutilisable… comme une expression ! Les opérateurs ?:,
&& et || ajoutent un peu de variété à votre éventail de
possibilités… mais rappelez-vous qu’il faut employer avec && ou
|| des opérandes à valeur entière (ou en tout cas qu’il est
possible de convertir en entier).
Et un exemple bidon :
#define GETARG(argcptr, argvptr) (--*(argcptr), ++*(argvptr))
#define CMP(p, q) ((p) == (q) || cmpfunc((p), (q)) == 0)
Remarquez le problème des effets de bords potentiellement provoqués
par l’évaluation de p ou q, dans la seconde macro ; nous allons
y revenir…
for !
Mais les macros ne sont pas limitées à remplacer des fonctions, elles peuvent également être utilisées pour créer de nouvelles structures de contrôle.
À la différence d’un appel de fonction, l’utilisation d’une structure de contrôle inclut un (ou plusieurs) blocs de code. Le schéma de base simplifié est le suivant :
BEGIN (/* ... */) {
/* ... */;
} END (/* ... */);
Il faut bien sûr remplacer BEGIN et END par des macros
appropriées.
En utilisant des boucles for pour implémenter votre structure de
contrôle, vous pouvez souvent omettre la partie END, et ainsi
alléger l’usage de vos macros.
Un exemple de telles constructions peut être trouvé dans
sys/queue.h ; dans cet exemple, on parcourt une liste
(implémentation typique : une boucle for) :
LIST_FOREACH (var, head, next) {
/* 'var' pointe successivement sur chaque élément. */
/* ... */;
}
Un autre exemple, de structure plus complète, munie du marqueur de fin, peut être trouvé dans les bibliothèques standards de Plan 9 ; le code suivant permet de traiter les options de la ligne de commandes :
ARGBEGIN {
case 'a':
/* Option '-a' spécifiée. */
/* ... */;
break;
case 'b':
/* ... */;
break;
default:
usage();
} ARGEND;
La concaténation est un mécanisme puissant puisqu’elle permet de générer des identificateurs à partir de fragments, dont certains peuvent être passés en paramètres à vos macros.
Moyennant le respect de quelques conventions dans les noms, vous pouvez écrire du code générique, dont les parties spécifiques sont masquées derrière un espace de nom improvisé passé en argument.
Et encore un exemple bidon pour illustrer le principe de base :
#define RELEASE(name, p) do { \
if (name##_decrref(p) == 0) \
free(p); \
} while (/* CONSTCOND */ 0)
Rappelez-vous l’histoire des effets de bord que nous avons rencontrée plus haut. Il n’y a pas de méthode magique, pour éviter les effets de bords, il faut passer par des variables intermédiaires.
Une solution simple et plutôt élégante pour résoudre ce problème est de générer non plus du code directement, mais des fonctions… Mais, mais, me direz-vous, quel intérêt de passer par des macros pour générer des fonctions ? Pourquoi ne pas écrire les fonctions directement ?
Et bien, cette astuce, combinée à la précédente, permet un peu de généricité ! Et un ptit exemple pas très utile, pour la route :
#define ARRAY_FIND_PROTOTYPE(name, type, cmp) \
type *name##_ARRAY_FIND(type *, size_t, type *)
#define ARRAY_FIND_GENERATE(name, type, cmp) \
type *name##_ARRAY_FIND(type *_a, size_t _n, type *_elm) \
{ \
for (; _n > 0; ++_a, --_n) { \
if (cmp(_a, _elm) == 0) \
return _a; \
} \
return NULL; \
}
#define ARRAY_FIND(name, a, n, elm) \
name##_ARRAY_FIND((a), (n), (elm))
Pour des exemples plus complets, je vous invite à jeter un coup d’œil
à l’implémentation de `sys/tree.h` (ou localement, dans
/usr/include/sys/tree.h si vous avez un BSD sous la main).
Côté performances, vous n’avez guère de soucis à vous faire. Si vous
définissez des fonctions statiques, le compilateur s’occupera tout
seul comme un grand de les machiner comme il se doit. Et si vous
voulez lui forcer un peu la main, inline est là pour ça.α
α : Mais c’est du C99… je reviendrai probablement sur cette question dans un prochain article.
Nous arrivons ainsi à la dernière astuce accessible avec un préprocesseur à la norme C89.
Vous connaissez certainement les directives #if, #ifdef, et
compagnie. Hélas, elles ne peuvent être utilisées à l’intérieur
d’une définition de macro.
Dans les cas simples — mais très courants — où vous souhaitez simplement discriminer entre plusieurs valeurs d’une constante passée en paramètre, cependant, vous pouvez vous en sortir en utilisant… la concaténation ! Encore elle !
En effet, il suffit pour cela de définir chaque alternative comme une macro séparée, nommée avec un préfixe commun, et un suffixe dépendant du cas. Illustration :
#define DECL_STATIC_0
#define DECL_STATIC_1 static
#define DECL_STATIC(s) DECL_STATIC_##s
#define SOME_FUNCTION_PROTOTYPE(name, type, s) \
DECL_STATIC(s) type some_function(type);
Avec C99 ont été introduites les macros variadic, c’est-à-dire acceptant un nombre variable d’arguments. Cet ajout, a priori mineur, est en vérité très important, car il permet la manipulation des n-uplets, soit des collections de valeurs.
Pour nous, un n-uplet sera une suite d’éléments séparés par des virgules, entre parenthèses. Par exemple :
(a, b, c)
Pour opérer sur des tuples, le truc de base à remarquer est le suivant :
les appels de macros respectent l’équilibrage des parenthèses (ce qui permet effectivement de passer des n-uplets comme simples arguments à des macros) ;
les macros sont développées autant de fois que possibles : si le texte substitué par une macro contient encore des invocations de macros, celles-ci sont traitées (sauf si elles ont déjà été appelées) ;
en juxtaposant un n-uplet à un nom de macro, on obtient un appel de la macro correspondante, avec en guise d’arguments les éléments du n-uplet !
Il faudrait un billet entier pour explorer en profondeur toutes les possibilités offertes par cette astuce. Je ne vais présenter ici qu’un cas d’utilisation simple, mais sachez qu’il est possible, par exemple de créer une macro qui substitue tout n-uplet par 1 et toute autre construction par 0, par exemple ! Je vous laisse imaginer ce que l’on peut en faire, combinée aux alternatives par concaténation expliquées ci-dessus.
Mais revenons à notre modeste exemple, qui illustre la fonction identité de manière ridiculement complexe :
#define IDENTITY(...) __VA_ARGS__
/*
* 'IDENTITY' peut en fait servir à supprimer des parenthèses
* superflues gênantes.
*/
#define CALL_WITH_RESOURCE(name, var, aargs, f, args) do { \
name##_type var = name##_alloc aargs; \
f(var, IDENTITY args); \
name##_free(var); \
} while (/* CONSTCOND */ 0)
#define FILE_type FILE *
#define FILE_alloc fopen
#define FILE_free fclose
/* ... */
CALL_WITH_RESOURCE (FILE, fp, ("log.txt", "a"),
fprintf, ("%d\n", 42));
COS contient une bibliothèque entière de macros travaillant sur les
n-uplets. Je vous invite à regarder le code source si cela vous
intéresse ; il s’agit plus particulièrement du dossier
CosBase/include/cos/cpp/, dans l’archive.
Au vu de ce que nous venons de voir, vous êtes en droit de vous demander : « Peut-on faire pire ? » Et la réponse est oui ! :) J’ai gardé cette astuce pour la fin car elle constitue, à mes yeux, une limite que je ne souhaite pas, à titre personnel, franchir.
Un peu plus haut, j’ai dit quelque chose d’important : dans une chaîne de substitutions de macros, une macro déjà substituée ne le sera plus, même si elle apparaît dans le texte produit… il est donc impossible de faire des macros récursives !
L’astuce ici consiste à dire : « L’univers est fini, je vais décrire tout l’univers ! » Aidée de la concaténation, les possibilités sont vraiment (in)finies ! Mais je ne m’étendrai pas davantage sur le sujet. Encore une fois, COS contient toute une panoplie d’exemples, qui, je n’en doute pas, apparaîtront brillants pour certains, et affligeants pour d’autres.
Voilà, en espérant vous avoir appris quelques petits trucs rigolos ! Have fun!
Récemment, on m’a demandé de composer quelques vers pour le Post’IT, le journal de l’école. Et puisque je me suis remis à écrire un peu, je me permets de poster ici un vieux billet, vieux d’octobre dernier !
En octobre dernier donc, dans le sombre hall de l’Ensimag, près de l’endroit qui sent le paté… On me fit remarquer, là, dans un coin, accroché sur le panneau réservé aux associations, un poème. Et quel poème ! « L’amoureux ! » Il fallait réagir ! Ramener à ce micro-monde la Vérité !
Et le lendemain, ce fut chose faite ; encore aujourd’hui, vous pouvez trouver, accroché près de ces vers, les miens, signés de ma vraie fausse signature. Pour ceux qui ne peuvent malheureusement se déplacer, voici tout de même, en guise de consolation, mon œuvre ! :-°
L’infirme
Je suis l’amoureux !
Là, ne vois-tu pas, dans mes yeux ?
Cette larme de mots, invisible,
Demandant à mouiller ton reflet ?
Et là, les chants muets !
N’agonisent-ils pas, à mes lèvres
suspendus,
Guettant une illusion de ta gorge
dévêtue ?Ne suis-je pas l’amoureux ?
Au fond de mon lit, malheureux,
Bercé de désirs impossibles.
Dérivant de fantasme impuni
En crime inassouvi,
Ne devines-tu pas où l’esprit vagabonde,
Égaré par ses propres passions infécondes ?Suis-je l’amoureux ?
L’aube m’est venue, le cœur creux.
Ma plume est sèche,
Mes pages défigurées ;
J’y parle d’amours éthérées.
Ton image,
ce doux spectre, toujours me poursuit ;
Mais déjà,
mon regard se confond dans l’ennui.Pourrais-je être l’amoureux ?
Tu le sais ! Dis moi ! Je le peux.
Ta chair est fraîche,
Et ma nature invincible.
Je sais que les voix inaudibles
Souffleront à mes sens, quand enfin viendra l’heure,
De m’en faire à nouveau le gardien, le voleur.
Comme promis, voici la seconde partie de mon article traitant de ce sujet ô combien passionant qu’est… mon blog. Finies la conception et les questions programmatiques profondes, et place à l’action, au déploiement, à la publication, au référencement ! Autant de sujets qui pourront surprendre le lecteur habitué de ce blog.
Mais pourquoi s’embêter avec tout ça, me souffle-t-on ? On a développé un produit, à n’en pas douter, technologiquement révolutionnaire… il faut maintenant le vendre ! Et pour un blog, pour un site Internet, pour un projet amateur tel que celui-ci, se vendre, cela signifie avant tout se faire connaître, et plus précisément, dans notre cas, être lu.α
Dans ce petit billet, je vous propose un tour d’horizon des techniques que j’ai appliquées à mon blog, dans l’optique d’améliorer la navigation, pour les humains, comme pour les robots. Ces optimisations sont adaptées au modèle que j’ai choisi. Elles ne conviendront certainement pas à tout le monde, ou à tous les contextes. Mais si vous aussi vous tenez un blog, ou un petit site à but informatif, que vous êtes jeunes et candides, face à la perversion grandissante du Web, peut-être cet article saura-t-il vous apporter quelques réponses… à des questions que vous ne vous êtes sans doute jamais posées. :)
α : Je n’ai pas l’intention de m’étaler davantage sur la question, simplement, s’il est plus important pour bluestorm et pour moi d’être lus que d’engranger des visites sur le blog, c’est très simple : plus de trafic signifie plus de travail pour le serveur et plus de bande passante. Et comme il ne s’agit que d’un petit blog, nous n’avons rien à vendre, ni produits, ni publicité, à quoi bon gaspiller nos ressources ?
La première question qui se pose, une fois le blog codé, est de savoir où l’héberger. J’ai opté pour un hébergement à la maison, c’est-à-dire sur une de mes machines, derrière ma Freebox. C’est une décision critiquable, étant donné la petite bande passante dont dispose ainsi le site, mais jusqu’à maintenant, la faible fréquentation du blog ne s’est pas révélée être un problème pour ma connexion.
En contrepartie, l’hébergement chez soi permet une grande flexibilité. On configure soi-même la bête, on y déploie la plateforme que l’on veut : serveur HTTP, SSH, langages de script, bibliothèques appropriées, etc.
Mais si le cœur du blog, c’est-à-dire le script de génération des
pages HTML, est toujours servi depuis chez moi, un des changements
majeurs de cette nouvelle mouture est l’externalisation des ressources
statiques. Ainsi, lorsque vous naviguez sur le site, les feuilles de
style et les images sont en réalité téléchargées depuis
media1.huoc.org, un domaine pointant sur un petit compte gratuit que
j’ai pris chez alwaysdata. Le bénéfice est double : d’une part,
je me décharge ainsi d’une petite moitié du trafic lié à chaque
nouveau visiteur (les anciens ayant déjà ces fichiers en cache),
d’autre part, les requêtes HTTP sont réparties entre deux hôtes (de
manière certes inégale : plus de données d’un côté, plus de requêtes
de l’autre).
De même, le flux Atom principal du site est maintenant réchauffé par FeedBurner, ce qui, au passage, me permet de collecter quelques statistiques.
Bien sûr, avant même de s’occuper de cela, la meilleure optimisation
pour gagner un peu de bande passante passe d’abord par la prise en
charge de la mise en cache (en-têtes HTTP Last-Modified,
If-Modified-Since et Cache-Control) accompagnée d’une compression
gzip du contenu textuel.
Une autre astuce que j’utilise, enfin, est l’incrustation d'images dans le CSS pour réduire le nombre de requêtes HTTP.
Tout cela combiné me permet de vous présenter le contenu de la page principale du blog en à peine plus de 50K (dont 10K sont pris par le script JavaScript de Google Analytics…) d’images et de texte compressé, ce qui, entre nous, est mieux que la plupart des blogs que j’ai pu voir ; ceux basés sur Wordpressβ tournent généralement autour de 200K.
β : Wordpress, dont la configuration par défaut ne semble guère se préoccuper que de la compression de la page HTML ; le cache est sans doute en option, ce qui est à mon sens dommage. Les autres éléments de la page (CSS, JS, images) sont quand à eux bien souvent délaissés, ce qui est une erreur, car ils constituent, d’autant plus pour des sites à l’apparence sophistiquée, un trafic tout à fait non négligeable.γ On pourrait cependant argumenter qu’il n’est pas du ressort du logiciel de blog de fournir la configuration des fichiers statiques.
γ : Pour plus d’informations, vous pouvez aller voir, par exemple, le portail de Google dédié à la question. Tous les conseils qui y sont données ne sont pas forcément bons à prendre, je dirais, mais dans l’ensemble, on y trouve des choses intéressantes, et des liens vers des outils efficaces (YSlow, PageSpeed, etc.).
Le blog est maintenant en place, on peut y accéder, tout va bien, mais le plus dur reste à faire : le faire connaître. Honnêtement, avec la modeste fréquentation de mon blog, ce n’est probablement pas moi qui vais vous donner des leçons concernant le référencement. Si j’ai retenu une chose de mes lectures et de mes expérimentations, cependant, c’est certainement ceci : présenter du contenu correctement structuré par un HTML simple et sémantique, avec des URL courtes et sympas contenant quelques mots-clés, est agréable à naviguer, et plutôt bien récompensé par Google. Que demande le peuple ?
Ce qu’il demande, généralement, c’est des liens retour, les fameux liens retour… avoir des gens qui font référence à vos articles, si ce n’est pas là un symbole de réussite ! Hélas, il n’est pas aisé de stimuler l’enthousiasme au point que l’on veuille vous citer.
On peut bien sûr faire un peu de publicité par-ci, par-là, en insérant un lien discret dans ses profils, sur les forums, ou autres réseaux sociaux que l’on fréquente, mais les liens ainsi générés risquent d’être relativement médiocres (en revanche, c’est un bon moyen d’amener des visiteurs).
Ou on peut s’inscrire sur des annuaires… Option que j’ai envisagée à plusieurs reprises sans jamais vraiment me décider. J’ai mis longtemps avant de franchir le pas pour dmoz, et c’est pourtant le plus gros annuaire ! Il y a plusieurs raisons à cela. D’une part, la plupart des annuaires que j’ai pu voir (et j’en ai parcouru un rayon) ont un air très amateur. Parmi les rares qui sont visuellement acceptables, il y a souvent le problème du voisinage : certains annuaires ont trop de liens entassés les uns sur les autres sans hiérarchisation, d’autres souffrent de problèmes de modération, ou parfois, on se heurte tout simplement à des fluctuations de la qualité des liens au sein d’une même page ou d’une même catégorie, qui laissent dubitatif (vais-je me retrouver à côté de ce vilain site-là ? ou de celui, qui a l’air plus correct ?).
Voilà qui conclut ma brève non-série d’articles portant sur mon blog. Et après toutes ces semaines de développement Web, je suis impatient de reprendre une activité plus… normale. Il reste certes encore quelques bugs, mais il est grand temps pour moi de changer d’air…
Voilà un petit mois que j’ai commencé mon stage en laboratoire, stage facultatif entrant dans mon cursus Ensimag. Pour les curieux, je suis chez TIMA, un labo en plein centre de Grenoble. Durant ce stage, je suis censé étudier la prise en charge de processeurs VLIW par QEMU, en me basant sur l’exemple du C64x+, un DSP conçu par Texas Instrument. L’objectif est d’établir une méthode convaincante, et un petit prototype qui l’illustre sur le C64x+.
Pour l’heure, je ne peux que constater que j’ai un mal certain à m’adapter à QEMU. Ce n’est pas tant à cause de la documentation interne quasi inexistante, ça, disons, j’ai l’habitude avec le code libre. Mais la structure même du programme est à mon sens quelque peu obscure : de la redondance entre modules similaires, une isolation architecturale qui me paraît douteuse, et des conventions qui semblent au premier abord très peu intuitives.α
α : J’entends d’ici la horde de fanboys en puissance qui se prépare à me lyncher. Disclaimer! Ce n’est pas que je dénigre le travail accompli, ou les mecs derrière tout ça, qui, à n’en pas douter, sont doués, géniaux, sublimes, etc. plus que je ne le serai jamais… Bref, je ne fais que constater que, du point de vue du gars qu’on emploie (gratuitement) à hacker ce code, c’est pas évident…
Dans ce premier article, donc, je vous propose simplement une petite présentation de QEMU et de sa technique d’émulation, du moins, ce que j’en ai compris. :)
Et ouais, QEMU, ce n’est pas WINE. Car QEMU, c’est un émulateur, un programme capable d’exécuter du binaire destiné à d’autres machines. Dans la petite jungle des solutions d’interopérabilité binaire, l’émulateur est sans doute celui qui se place au niveau le plus bas puisqu’il lit et imite une architecture directement au niveau de son jeu d’instructions. QEMU se différencie en cela de logiciels de virtualisation, tels que Xen, dont j’ai déjà parlé dans un billet précédent, dont le but est de faire tourner des systèmes différents mais conçus pour le même CPU, sur une même machine.
Le travail d’un émulateur est donc de lire et d’exécuter un code binaire ; en l’occurrence, il s’agit du codage des instructions d’une machine réelle, mais cela pourrait tout à fait être une machine abstraite. Au final, les techniques possibles sont semblables à celles que l’on peut imaginer pour un compilateur. Simplement, ici, le langage source est binaire (et le langage cible aussi). L’analyseur « syntaxique » se voit donc remplacé par une espèce d’automate dont le rôle est de décoder le binaire en entrée, à la manière du processeur que l’on imite. On y gagne a priori en simplicité : les formes que peuvent prendre les instructions sont généralement assez régulières et limitées. Mais les subtilités guettent ! Pour peu que le jeu d’instructions soit un peu étoffé, les cas particuliers se multiplient rapidement. De plus, il faut prendre en compte l’état de la machine ; en terme de langages, cela se traduirait par un contexte.
Une fois l’instruction identifiée… reste à l’exécuter. Là encore, comme pour les langages de programmation, le choix s’offre à nous, notamment entre interprétation et compilation. Et QEMU a choisi…
… la traduction binaire dynamique.β En quelques mots, il s’agit d’une compilation à la volée vers du code exécutable par le processeur hôte (faisant tourner QEMU lui-même). Ce code est ensuite directement exécuté. Et hop ! On a un truc pas trop lent. C’est grosso modo le même principe que dans les JIT que l’on trouve dans les machines virtuelles de langages de haut niveau, et dont, il y a quelques années, tout le monde était trop fier.γ
β : Wikipédia donne « translation dynamique de code », mais désolé, je ne vois aucun avantage à utiliser « translation » ici à part se donner l’air plus analphabète qu’on ne l’est. :] Plus sérieusement, il me paraît douteux qu’un tel terme soit entré dans l’usage, aussi, je ne me sens pas obligé de l’employer…
γ : Then some Lisp defenders came and ruined the fun…
Plus en détails, QEMU est composé de modules implémentant chacun l’émulation d’un processeur donné, appelé processeur cible (target), en opposition au processeur hôte, sur lequel tourne QEMU, et vers lequel les instructions vont être traduites. La responsabilité d’analyser l’entrée binaire est déléguée au module.
Celui-ci lit l’entrée par blocs qu’il traduit dans un langage intermédiaire, composé d’un jeu de micro-opérations minimal. Un bloc est une suite simple d’instructions, qui s’exécutent linéairement, les unes après les autres. Concrètement, cela signifie que le bloc prend fin au premier saut. Ce code intermédiaire est transmis à TCG, le générateur de code de QEMU, qui se charge de le traduire pour l’hôte. Cela ressemble à s’y méprendre à de la compilation, n’est-ce pas ? Ici, on retrouve les avantages usuellement attribués à l’usage d’un langage intermédiaire : abstraction, uniformité, simplicité.
Et bien moi, dans tout ça, je travaille surtout sur la partie analyse et génération du code intermédiaire. L’objectif de mon stage est d’élaborer une méthode de traduction systématique de jeux d’instructions VLIW vers le langage intermédiaire de QEMU. L’idée serait d’automatiser les aspects fastidieux liés à la multitude de cas possibles introduite par le parallélisme. Ainsi, à partir d’une description abstraite du jeu d’instructions, j’espère pouvoir générer le code qui effectue réellement la traduction.
À l’heure où j’écris ces lignes, je commence tout juste à m’attaquer à la partie traduction. J’ai passé ce dernier mois (du moins les jours où je travaillais dessus) à faire connaissance avec QEMU, et, dans une moindre mesure, l’architecture du C64x+. Pour l’heure, je n’ai produit qu’un squelette plus que minimaliste de target C64x+, dont je pense reparler bientôt, dans un prochain article technique consacré au récit de mon (début de) périple dans les entrailles de QEMU !
Après deux semaines de développement (et une semaine de déploiement qu’il serait dommage d’oublier…), le nouveau blog est là. Pour les pauvres âmes égarées par erreur en ces lieux, permettez-moi de rappeler que ce blog, sur lequel vous êtes, est un produit entièrement artisanal, issu de nos belles campagnes, ahem, je me dissipe.
Mais si je vous écris aujourd’hui, ce n’est pas pour vous parler des nouvelles fonctionnalités trop cools du blog (vous pouvez bien voir par vous-mêmes, vous êtes assez grands), mais pour vous faire part de cette petite expérience, la mienne, dans le monde du développement Web. Au programme donc, aujourd’hui : comment écrire un blog, ou plutôt, comment j’ai écrit le mien. Original, n’est-t-il-pas ?α :]
Avant de poursuivre, il faut replacer la démarche dans son contexte (wow, je croirais entendre mes profs). Mon blog est un petit blog, hébergé en grande partie sur mon PC de bureau reconverti en serveur malgré lui, tout ça derrière une Freebox. Il est clair, à partir de là, que les enjeux ne sont pas du tout comparables à un site moyen ou gros comptant les visiteurs par milliers.
α : Ça me fait penser à ce petit billet gentillet sur lequel j’étais tombé par hasard, un jour, alors que je dérivais de recherches en recherches puis de liens en liens, sur l’Internet : Blogging apps are the new Hello World.
Dans le fond, un blog, c’est quoi ? C’est un tas d’articles, de commentaires (si le blog n’est pas trop perdu), et toute une taxinomie de ce vaste petit monde en tags, catégories, séries, ou tout autre mode de classification que l’on pourra imaginer.
Besoins très classiques et à la fois pas si évidents à modéliser correctement.
Bien sûr, le but, du moins le mien, n’est pas d’implémenter tous les outils de filtrage et de tri possibles et imaginables. J’ai toujours pensé, par exemple, que les tags et les catégories étaient redondants. Je ne doute pas que certains y trouvent leur compte, à séparer en tags et en catégories ; moi, honnêtement, avec mon modeste blog, je n’en vois pas l’intérêt. Il n’y a tout simplement pas suffisamment de contenu pour nécessiter autant de sophistication.β
J’ai donc retenu les éléments suivants :
β : Du temps des RZ-pages, mon ancien site, le système de catégorie était autrement plus complexe, conceptuellement, que l’actuel classification par tags : il était hiérarchique, et les articles pouvaient en même temps être des catégories (duh!). L’expérience a montré que pour le contenu que j’avais à proposer, c’était plus confus qu’autre chose.
γ : Du point de vue de l’implémentation, séries et catégories sont proches, si l’on exclut la possibilité de joindre des articles consécutifs d’une série.
Durant les dernières vacances (ou était-ce avant ?), j’ai eu une discussion intéressante sur #sdz, sur l’organisation des commentaires. Certains argumentaient que les commentaires hiérarchiques étaient plus clairs, tandis que je restais sceptique.
Mais si je vous parle de cela, c’est qu’un tel choix n’est pas sans conséquences sur l’implémentation. Si l’on opte pour une vue généralisée en arbres des commentaires, il vient naturellement que l’on pourrait faire de même pour les articles, et quitte à faire, ne maintenir qu’un seul arbre gigantesque comprenant toutes les séries, les articles, et les commentaires. Une telle stratégie suggère une représentation spécialisée des données, adaptée à la consultation d’arborescences, telle que la représentation intervallaire.
Pour mon blog, j’ai décidé de rester sur un modèle plus simple, avec
une hiérarchie figée, à trois niveaux : séries, articles,
commentaires. Se pose tout de même la question de faire une seule ou
plusieurs tables ; j’ai décidé de n’en faire qu’une. Un article, une
série, ou un commentaire est, dans ma base de données, un
enregistrement dans la table entry. Il y a à cela plusieurs raisons,
qui peuvent finalement toutes se résumer à un mot :
homogénéité. J’aime pouvoir traiter les articles et les commentaires
de manière similaire… car ils sont similaires ! L’interface est
différente (voy. ci-dessous : Rédaction, mises à jour, et
Atom), mais les données sont analogues : des auteurs, un
contenu, une date, et quelques autres broutilles.
Mais la conception ne s’arrête pas là : on ne va guère loin avec juste des articles, des commentaires, et des séries… reste à les lier entre eux ! Et c’est là où les choses se gâtent quelque peu. Résumons les différents types de liens qui peuvent lier deux objets :
En SQL, cela donne ceci :
CREATE TABLE entry (
-- Un identifiant :
e_eid INTEGER PRIMARY KEY,
-- Des métadonnées :
e_atomid TEXT UNIQUE NOT NULL,
e_title TEXT NOT NULL,
e_ctime INTEGER NOT NULL,
e_mtime INTEGER NOT NULL,
e_lang TEXT,
-- Quelques liens :
e_parent INTEGER,
e_prev INTEGER,
e_next INTEGER,
-- Le contenu :
e_content TEXT,
-- D'autres métadonnées (relatives au site) :
e_url TEXT UNIQUE,
e_type CHAR(1) NOT NULL, -- Series | Article | Topic | Comment.
e_from CHAR(1) NOT NULL, -- Primary | Secondary | Foreign.
e_banned CHAR(1) NOT NULL, -- Auto-banned | Banned | -.
e_level INTEGER NOT NULL,
-- D'autres champs...
);
De plus, il est fréquent, quand on récupère les données d’un objet, d’avoir besoin de certaines informations connexes, telles que le nombre de commentaires d’un article, ou le titre de la série apparentée. Avec une base de données relationnelle, on a des jointures. C’est un joli concept, dirait bluestorm, mais une jointure pour la série parente, une pour l’article précédent, une autre pour l’article suivant, et encore une pour les commentaires associés, cela fait… beaucoup.
C’est là qu’intervient le cache. Enfin, un cache ; car il y a toute une flopée de façons de faire. On peut mettre en cache le résultat de certaines requêtes, ou carrément la sortie HTML produite par notre script. On peut stocker ça en mémoire (p.ex. en utilisant memcached), ou dans la base de données. Sans compter les mille et une manières de maintenir ce cache à jour.
Dans mon cas, il s’agit tout simplement d’une petite table,δ dont les données sont extraites à partir d’une vue associée. C’est la méthode du pauvre pour matérialiser une vue, disons.
δ : Il y a également un cache au niveau de la sortie HTML (ou XML pour Atom et les sitemaps).
Et pour ceux qui aiment le code, voici :
-- La vue :
CREATE VIEW entry_cache_1 AS
SELECT
self.e_eid AS ec_eid,
parent.e_url AS ec_parent_url,
parent.e_title AS ec_parent_title,
parent.e_atomid AS ec_parent_atomid,
parent.e_authors AS ec_parent_authors,
parent.e_tags AS ec_parent_tags,
thread.et_utime AS ec_parent_utime,
prev.e_url AS ec_prev_url,
prev.e_title AS ec_prev_title,
prev.e_atomid AS ec_prev_atomid,
next.e_url AS ec_next_url,
next.e_title AS ec_next_title,
next.e_atomid AS ec_next_atomid
FROM
entry self LEFT JOIN
entry parent ON self.e_parent = parent.e_eid LEFT JOIN
entry_thread thread ON self.e_parent = thread.et_eid LEFT JOIN
entry prev ON self.e_prev = prev.e_eid LEFT JOIN
entry next ON self.e_next = next.e_eid;
-- La table :
CREATE TABLE entry_cache (
ec_eid INTEGER PRIMARY KEY,
ec_parent_url TEXT,
ec_parent_title TEXT,
ec_parent_atomid TEXT,
ec_parent_authors TEXT,
ec_parent_tags TEXT,
ec_parent_utime INTEGER,
ec_prev_url TEXT,
ec_prev_title TEXT,
ec_prev_atomid TEXT,
ec_next_url TEXT,
ec_next_title TEXT,
ec_next_atomid TEXT
);
-- Et la requête pour remplir la seconde depuis la première :
REPLACE INTO entry_cache SELECT * FROM entry_cache_1
WHERE ec_eid = ?
En vérité, il manque sur cette démonstration certaines données
capitales telles que le nombre de commentaires d’un article. Celles-ci
sont en fait maintenues à part, dans une table entry_thread. Cette
dernière est mise à jour par incréments, à chaque ajout d’un nouveau
commentaire. Elle peut également être rafraîchie à partir d’une
requête, à la manière d’entry_cache.ε
Des approches plus complexes (principalement du fait de leurs possibilités de passage à l’échelle) peuvent se justifier pour des gros sites, mais compte tenu de mon hébergement quelque peu technologiquement précaire, pour parler comme Poulet, la ressource limitante est clairement la bande passante, et s’il est rigolo de jouer au petit jeu de l’optimisation un moment, on peut dire que j’aurais déjà perdu assez de temps comme cela. :)
ε : C’est utile, par exemple, pour l’importation de commentaires sous forme d’un flux Atom archivé, comme cela a été le cas lors de la migration vers la nouvelle version du blog.
La question du stockage et de l’indexation des tags est l’autre gros problème d’implémentation. Le sujet est plutôt bien présenté, benchmarks à l’appui, dans cet article sur la représentation des tags dans une base de données ; je ne vais pas épiloguer sur les différentes manières de faire. Pour résumer, on a le choix entre stocker les tags d’un article de manière dénormalisée avec l’objet lui-même, ou utiliser des tables d’associations entre des objets articles et des objets tags, avec toutes les variantes possibles et imaginables.
L’ancienne version du blog utilisait la forme dénormalisée… essentiellement par flemme. Le problème qui s’est posé fut celui de faire des statistiques (p.ex. des nuages de tags) sur les tags. Cette approche est fondamentalement asymétrique et privilégie l’accès par articles au détriment de l’accès par tags.
On pourrait imaginer maintenir un index orthogonal, qui recense tous les articles associés à chaque tag. Avec des structures de données appropriées, l’intuition me dit que l’on devrait arriver à queque chose de pas mal. Je n’ai pas tellement étudié la question, mais il me semble logique que les mecs qui se branlent sur les bases de données non relationnelles, dans lesquelles la redondance est reine, soient enclins à opter pour ce genre de solutions. Quelqu’un au courant pour m’expliquer à quel point j’ai tort ?
Bref, pour revenir à des choses plus modestes, l’architecture actuelle
du blog utilise également un modèle redondant : les tags sont stockés
à la fois dans la table des articles, sous forme dénormalisée, et dans
une table tag, à part, associée à entry par la table d’association
tagmap :
CREATE TABLE entry (
-- Champs précédemment décrits...
-- Listes d'auteurs et de tags dénormalisées :
e_authors TEXT,
e_tags TEXT,
-- D'autres champs...
);
CREATE TABLE tag (
t_tid INTEGER PRIMARY KEY,
t_name VARCHAR(255) UNIQUE NOT NULL
);
CREATE TABLE tagmap (
tm_eid INTEGER NOT NULL,
tm_tid INTEGER NOT NULL,
PRIMARY KEY (tm_eid, tm_tid)
);
Le dernier point que j’aimerais aborder est une des particularités de mon blog. En effet, on n’écrit pas ses articles directement sur le site Web ! Mais ce n’est pas un blog entièrement statique non plus…
Mon blog est une espèce hybride qui se nourrit de fichiers Atom. En fait, quand bluestorm ou moi poste un billet, il l’ajoute à un fil Atom interne (grâce à un jeu d’outils en ligne de commandes développés en parallèle), et ping le blog pour lui demander de rafraîchir sa mémoire.
Cela peut paraître un peu compliqué pour pas grand chose, au premier abord, mais l’intérêt de cette méthode est qu’elle découple la rédaction et la présentation du contenu… Pas convaincus ? :p Accessoirement, la généralisation de cette approche permet d’importer du contenu d’autres blogs, le but étant au final d’obtenir une espèce de gros annuaire de billets intéressants affiliés à la micro-communauté #sdz.ζ
ζ : Le concept est différent d’un Planet, qui agrège du contenu trié par date ; ici, le but serait de hiérarchiser, classer les choses intéressantes dans un index global.
Je rappelle au passage aux intéressés, qui se reconnaîtront, que la spéc du format Atom augmenté utilisé pour cette opération de référencement se trouve ici : atom.text
Voilà qui conclut la première partie de cet article. Il me reste pas mal de choses à raconter ; des anecdotes plus légères, plus digestes sans doute pour certains, plus ennuyeuses pour d’autres. J’ai fini de parler de la machinerie derrière le blog. La prochaine fois, je parlerai de la partie directement visible : les questions d’interface, de présentation, de déploiement, tous ces petits ingrédients qui contribuent à créer des petites pages mignonnes et, je l’espère, agréables à lire, pour les visiteurs… sans oublier les robots, moteurs de recherches, et autres joyeusetés qui accompagnent immanquablement tout site Web dans sa vie, ou sa non-vie ! :]
C’est la saison des webbeux sur Ours & Hippy. Le blog fait peau neuve (qui ressemble beaucoup à l’ancienne, je vous l’accorde) et bluestorm vient nous parler (non sans aigreur et dédain pour l’aspect Web) de Macaque, sa petite création qui permet de marier en douceur OCaml et SQL… Quel rapport avec le Web, me direz-vous ? Parce que PHP/MySQL, hahaha… que je suis drôle. (Cette dernière proposition à elle seule me fait penser que je me conno-ifie…) —rz0
En ces périodes d’activité intense, il est de plus en plus difficile de prendre le temps d’écrire un bon gros billet comme on les aime. Je me permets de vous servir ici un billet un peu réchauffé : j’ai écrit un article sur Macaque, et j’en profite pour en parler ici.
Macaque a déjà été mentionné sur ce blog, mais sans vraiment d’introduction concrète. J’espère ici présenter Macaque de façon plus introductive, et surtout vous donner le lien vers l'article (18 pages) et vous inciter à le lire.
Qu’est-ce que c’est que Macaque ? Puisque la paresse préside ce billet, je me permets de citer, verbatim, le résumé et une partie de l’introduction de l’article.
Macaque est une bibliothèque OCaml permettant d’interagir avec un serveur SQL. Elle permet de construire des requêtes vérifiées statiquement, de façon modulaire. Une extension Camlp4 apporte une syntaxe concrète inspirée des compréhensions. Des types > fantômes sont utilisés pour encoder, en utilisant les types objets d’OCaml, certaines propriétés fines des valeurs SQL, comme la nullabilité.
Les bases de données sont des méthodes solides et reconnues de stockage d’information pour les besoins d’une application. Un programmeur qui voudrait s’en servir est cependant confronté à une situation désagréable : il doit communiquer avec un programme externe (le serveur de base de données) en lui envoyant des requêtes en format texte, c’est-à-dire oublier, dans cette partie de son programme, tout le confort des données structurées et des moyens d’expression de son langage.
La méthode la plus flexible pour construire des requêtes SQL est la production d’une chaîne de caractères correspondant à la requête :
let interroge table predicat = "SELECT * FROM " ^ table ^ " WHERE " ^ predicatSon inconvénient majeur et qu’elle transporte toutes les données sous forme de chaînes de caractères, qui n’apportent aucune information de typage, donc aucune vérification de correction (même syntaxique) à la compilation. En particulier, il y a facilement des problèmes de sécurité si des portions de la requête peuvent provenir d’un utilisateur malicieux du programme.
La méthode la plus sûre pour écrire des requêtes est de vérifier leur validité, au moment de la compilation, en interrogeant directement le serveur SQL, comme le fait le projet PG'OCaml :
let interroge predicat = PGSQL(dbh) "select * from ma_table where $predicat"Pour que cette vérification statique soit possible, la requête doit contenir assez d’information : le serveur PostgreSQL sait vérifier le type des données mais ne dispose ni d’un moteur d’inférence sophistiqué, ni d’une forme de polymorphisme. En particulier, on a dû préciser ici la table étudiée,
ma_table. On ne peut pas écrire de fonction génériqueinterroge, qui fonctionne sur n’importe quelle table. On a donc perdu en flexibilité.Quels sont les compromis acceptables ? Macaque est un langage de requête, embarqué dans OCaml, qui se veut à la fois sûr et flexible. C’est une extension syntaxique couplée à une bibliothèque logicielle à l’interface fortement typée, qui permettent d’écrire des requêtes modulaires en conservant la sûreté à laquelle sont habitués les programmeurs OCaml.
Macaque est un logiciel libre, disponible sur le site OCamlForge.
L’article présente Macaque à des utilisateurs potentiels (Oui, vous !), mais discute aussi de son implémentation. En particulier, la section dédiée au traitement de l’opération GROUP BY décrit les méthodes de métaprogrammation utilisées pour renforcer la sûreté statique permise par le typage.
J’ai écrit Macaque au cours d’un stage de deux mois dans le laboratoire PPS. Cela ne vous dit sans doute rien, mais beaucoup les connaissent indirectement, car ce sont eux qui hébergent la principale version en ligne du livre « Développement d’applications avec Objective Caml », DA-OCAML pour les intimes, un cours OCaml pas forcément très accessible pour les débutants, mais très complet et qui met les pieds dans le plat de la programmation fonctionnelle.
Une partie des gens de ce laboratoire travaillent sur un ensemble d’outils pour le développement Web dans le langage OCaml. C’est le projet Ocsigen, dont le but est grosso-modo d’apporter la beauté de la programmation fonctionnelle aux sauvages qui codent des sites Web. Comme vous pouvez vous en douter, la diffusion de leur travail est restée plutôt restreinte, mais il y a des choses intéressantes dedans et je vous invite à jeter un coup d’oeil ; personnellement, j’ai abandonné ce terrain depuis longtemps et je ne suis pas impatient d’y remettre les pieds.
Toujours est-il que le sujet qu’ils m’ont proposé était intéressant, puisqu’il combinait beaucoup d’OCaml, du typage, de la métaprogrammation, et plus globalement le travail sur la conception d’une partie d’un langage de programmation, ce qui reste un des points qui me plaisent le plus en informatique.
J’y étais encadré par Jérôme Vouillon, un type très sympathique qui a su m’aider, malgré sa terrible timidité. Sa connaissance du langage OCaml m’a parfois impressionné ; il m’a permis en particulier de résoudre un problème de typage assez velu :
module M : sig
type 'a t
val of_option : 'a option -> 'a t
val to_option : 'a t -> 'a option
end = struct
type 'a t = 'a option
let id x = x
let of_option, to_option = id, id
end
Ce module a l’air tout à fait inoffensif, mais il ne permet pas de
conserver le polymorphisme de la valeur None, naturellement de type
'a option :
# let pas_assez_polymorphe = M.of_option None;;
val pas_assez_polymorphe : '_a M.t = <abstr>
Une toute petite modification suffit. Voyez-vous laquelle ?
C’est d’ailleurs un problème que j’aurais dû reconnaître, puisqu’il est mentionné dans un article que j’ai déjà survolé plusieurs fois, Relaxing the value restriction (12 pages).
La rédaction de l’article a eu lieu après le stage proprement dit, et, malheureusement pour moi, sur mon temps libre. Je dirais que cela représente environ 40 heures de travail. Il n’est pas du tout exhaustif, et même pas aussi complet que je l’aurais souhaité : après la première phase de rédaction, une grande partie de l’effort de relecture/retravail avait pour but de le raccourcir pour respecter les contraintes de taille. En pratique, cela constitue à enlever les explications, certains exemples de code, une partie du contenu, et surtout à rogner le plus possible sur les espaces verticaux du document.
Chaque paragraphe a donc été soigneusement retravaillé pour être le moins compréhensible possible, tout en contenant toute l’information nécessaire pour que l’on puisse expliquer à un éventuel critique que s’il n’a pas compris quelque chose, c’est qu’il n’a pas lu assez attentivement. L’étape suivante, utilisée par les copistes avant le septième siècle, est de supprimer la ponctuation et les espaces entre les mots.
Une petite anecdote : le nom Macaque, légèrement incongru, puise sa légitimité dans la formule tout à fait naturelle « MAcros for CAml QUEries ». Je l’ai trouvé un soir, tard et fatigué, au cours d’une discussion sur IRC pendant laquelle quelques bonnes âmes ont subi et commenté mes tentatives douteuses d’obtenir une abbréviation rigolote en combinant les représentants du champ lexical du chameau. Je les en remercie (il me semble me souvenir de la participation, entre autres, de Dark-Side, Katen, lasts et Cygal).
Je continue à maintenir Macaque sur mon temps libre. La force de développement est donc plutôt réduite, puisque le temps que je lui accorde est faible, mais elle est pour l’instant largement supérieure à la demande : à part le projet Ocsigen, personne ne semble pour l’instant envisager d’utiliser Macaque, et je n’ai donc pas de demandes d’utilisateurs. Pas de retours, pas de développement.
J’ai encore quelques idées de choses à ajouter à Macaque. J’en ai un
peu parlé sur le canal IRC #ocaml, et flux m’a fait des remarques
qui ont conduit à l’ajout, par exemple, des valeurs par défaut
à l’insertion.
Je pense que Macaque est une bonne solution pour le problème qu’il essaie de résoudre : générer des requêtes vers une base de données depuis le langage OCaml. Pour cette raison (et aussi peut-être un peu d’ego), je serais heureux de voir plus de gens s’en servir, profiter de ses qualités, et se plaindre de ses défauts. Je n’ai pas non plus fait beaucoup d’efforts pour populariser Macaque et pousser des gens à s’en servir : j’envisage mollement d’écrire un message consistant à la mailing list OCaml, et ce billet fait lui aussi partie d’une timide campagne de propagande.
Pour ma part, je suis content du travail que j’ai effectué pendant mon stage, et de l’expérience que j’en ai retirée, et je ne m’inquiète pas trop pour le sort des utilisateurs de SQL et OCaml. En plus du projet PG’OCaml déjà cité, d’autres personnes essaient de combiner les deux, dont par exemple quelques divagations sur eigenclass.org, ou le projet Ocaml-orm-sqlite, qui a commencé exactement en même temps que mon stage, mais prend une approche très différente.
En bref, ce n’est pas moi, de mon point de vue, qui décidera du futur (ou pas) de Macaque. Si des gens s’en servent, tant mieux, et je le ferai évoluer vers quelque chose de plus complet, sinon ce n’est pas grave, de toute façon j’ai déjà beaucoup trop de choses à faire.