Benchmark et critique des performances de Francium

Auteurice : Arthur Pons

Temps de lecture : ~11 minutes


Le benchmark

J’ai eu l’idée d’écrire cette note en entendant parler d’11ty, un générateur de site statique. Il est écrit sur leur site

Fast Builds and even Faster Web Sites

Et on trouve une petite animation qui présente qu’11ty construit 4000 fichiers markdown en 2s, plus rapide que qu’Astro en 20 secondes, Gatbsy en 30 secondes etc. Le site omet qu’Hugo est plus rapide mais soit.

Ces résultats proviennent de ce benchmark.

Et Francium ? Testons d’abord les performances de cmark et lowdown, les deux parseur de markdown qui nous utilisons. En prenant les 4000 fichiers markdown du benchmark et en lançant les commandes :

time find -name '*.md' | xargs -n1 -P0 sh -c 'lowdown "$1" > "$1".html' --
xargs -n1 -P0 sh -c 'lowdown "$1" > "$1".html' --  4,84s user 1,05s system 307% cpu **2,291** total

et

time find -name '*.md' | xargs -n1 -P0 sh -c 'cmark "$1" > "$1".html' --
xargs -n1 -P0 sh -c 'lowdown "$1" > "$1".html' --  4,84s user 1,05s system 307% cpu **1,914** total

On tombe sur des temps d’environ deux secondes. Je trouvais surprenant que ces deux logiciels, écrits en C et dont le seul but est de convertir du md en html ne soient pas plus rapides qu’Hugo. Marc m’a rappelé que cette commande fait un fork par fichier et les forks c’est “couteux”.

Un fork c’est la création d’un nouveau processus par un autre. Nos commandes seront un processus qui, pour chaque fichier en créera un nouveau pour faire sa traduction. Créer un nouveau processus a un coût fixe. Il serait donc plus performant de créer un minimum de processus qui gèrent un maximum de fichiers chacun.

En l’occurence cmark peut prendre plusieurs fichiers en argument, on peut donc écrire

time cmark *.md
cmark *.md  0,08s user 0,07s system 29% cpu 0,528 total

ou pour lowdown tout concaténer

time cat *.md | lowdown
lowdown  0,15s user 0,07s system 32% cpu 0,689 total

Dans les deux cas on s’économise le fait de devoir créer 4000 fichiers mais pas celui d’écrire les données pour les voir dans la console. D’ailleurs avec :

time cmark *.md /dev/null
cmark *.md > /dev/null 0,08s user 0,07s system 29% cpu 0,093 total

on voit que la majorité du temps est passé à écrire le résultat.

J’identifie avec le code suivant

for i in $(seq 1 4000)
do
echo $i > $i.txt
done
./test  0,03s user 0,17s system 99% cpu 0,199 total

que cela prend un peu moins de 0,2 secondes de créer 4000 fichiers presque vides1 ce qui voudrait dire que l’on a la découpe approximative suivante :

opération temps (s)
traiter les données 0,1
créer les fichiers 0,2
écrire les données 0,22

En réalité j’en sais rien, je connais pas assez bien linux pour savoir si je mesure les choses comme il faut. Prenez tout ça avec des pincettes.

Quoi qu’il en soit nous apprenons de ce test que Francium ne pourra pas aller plus vite qu’environ une demie seconde pour 4000 fichiers. Pour en faire un benchmark assez équivalent j’ai retiré tous les alias de page, sauf %S et modifié les pages md du dépôt de benchmark pour qu’elles n’aient plus le

---
title: blablabla
---

et leur ajouter le shebang

#! page

et les rendre exécutables.

J’ai fait ça avec

  find -name '*.md' | xargs -n1 -P0 sed -i 's/^/#! page\n/;2,4d'
  chmod 755 ./*

J’ai vérifié au préalable, le titre n’est pas pris en compte dans le benchmark. Du moins l’HTML généré par Hugo et 11ty a une balise <title> vide. Une fois que tout est prêt on construit le site comme on le fait habituellement avec Francium2 (version qui utilise lowdown) :

time make -B -j
make -B -j  30,29s user 8,31s system 302% cpu **12,741** total

On est donc sur du six fois plus lent que la simple conversion en HTML depuis lowdown. Tentons de comprendre pourquoi en bidouillant, faute de savoir comment correctement diagnostiquer ce genre de choses.

Retirer les shebang et modifier le makefile pour faire

./page machin.md

ne change rien en terme de performance.

Supprimer l’appel à install en faisant directement une redirection semble économiser environ une seconde.

Supprimer l’appel à page en demandant à makefile de directement utiliser lowdown et en supprimant les heredoc %S (en gros ce qu’on a fait au début mais avec l’overhead de make) fait tomber le temps d’exécution à 3,7 secondes. C’est donc quelque part dans page que l’on perd de la performance.

J’ai ensuite repris depuis le début en supprimant la gestion des sections. Cela revient à s’économiser la création d’un dossier temporaire et d’un fichier temporaire3 par article. Ne plus créer le fichier temporaire qu’il faut ensuite relire pour insérer son contenu dans le layout fait gagner environ 2 secondes, make ne prend plus qu’environ 9,5 secondes. Ne plus créer le dossier temporaire fait gagner 3 secondes supplémentaires pour descendre à 6,5 secondes environ.

Il semblerait donc que notre façon de gérer le layout soit responsable de 3 secondes d’overhead sur 4000 fichiers. Pour que la comparaison tienne à peu près la route j’ai modifié le template pour qu’il soit identique à celui généré par Hugo. Je m’arrête là dans l’exploration des performances de Francium.

On en retient qu’à la louche et avec mes outils de diagnostiques très approximatifs, ce qui coûte est :

Modification Temps (s)
overhead make 1,7
la création du dossier temporaire 2
la création puis la lecture du fichier temporaire par section 3
l’appel à install 1
le template 3

Ce qui ajoutés bout à bout explique la différence entre les deux secondes de lowdown et les 12/13 de Francium.

Résultats finaux :

Générateur Temps (s)
Hugo 1,6
11ty 2
Francium (sans sections) 6
Francium 12
Le reste 20+

Et alors ?

Francium tel qu’il existe pour le site de Katzele est 6 fois plus lent que les deux générateurs de sites statiques les plus rapides et plus rapide que tous les autres. Dans une version allégée, pour un site avec une seule section que l’on souhaite alimenter de contenu il n’est “que” trois fois plus lent.

C’est significatif mais à mettre au regard du temps d’ingénieurie investit. Il est probable que le temps passé à construire Francium dans son ensemble est un ou deux ou trois ordres de grandeur inférieur au temps passé à rendre Hugo et 11ty performants.

De plus la comparaison de performances de logiciels est compliquée et hasardeuse. Déjà parce que cela dépend du matériel sur lequel les logiciels sont testés. Ensuite parce que les couvertures fonctionnelles ne sont pas les mêmes. Nous avons vu que l’intégration de fonctionnalités supplémentaires dans Francium pouvait avoir un fort impact sur sa performance. Qu’en est-il des autres outils ? Hugo et 11ty font beaucoup plus de choses que Francium mais le benchmark leur fait générer un site aussi simple que gros. Au vu de leurs performances, ils ne semblent pas ralentir quand leurs très nombreuses fonctionnalités ne sont pas effectivement utilisées. Comment Hugo et 11ty réagiraient-ils si l’on construisait des sites plus complexes ? Et Francium ? En l’absence de réponse à ces questions les benchmarks ne veulent pas dire grand chose.

Cela dit, pourquoi est-ce que ces logiciels font la promotion de leurs performances sur leurs pages d’accueil ? Au-delà d’une éventuelle volonté de savoir qui a la plus gro le plus rapide, possibilité à ne pas sous-estimer, on pourrait aller regarder du côté des générateurs historiques. On me racontait que fut une époque nombre de générateurs de sites étaient particulièrement “lents”. Il fallait attendre possiblement plusieurs dizaines de secondes pour faire la revu de ses modifications ce qui rendait l’itération lente et le travail fastidieux. Pour résoudre ce souci il semblerait que les communautés autour de ces outils aient opté pour de l’ingénierie logicielle supplémentaire faisant appel à nos matériels informatiques modernes très puissants. “Cela prend 30 secondes de reconstruire tout le site pour vérifier le rendu de notre nouvelle page ? Optimisons le code pour que les opérations aillent plus vite et fassent usage de toute la puissance des processeurs pour le faire descendre à deux secondes”.

Ce qui est intéressant c’est qu’en puisant dans une autre culture informatique, comme la notre par exemple, ce n’est pas une autre solution qui aurait été choisie mais le problème qui n’aurait tout simplement existé - ou suffisamment rarement pour que ça n’en soit pas un.

Illustrons cette idée en trois points :

  1. Dans le monde Unix le système de “build”4 traditionnel permet de ne reconstruire que le nécessaire.

Le système utilisé pour compiler de nombreux programmes dans le monde Unix (et sur lequel Francium se base), make, utilise un graph de dépendance pour savoir ce qui doit être reconstruit et ce qui est encore à jour. Ainsi, si l’on modifie 10 fichiers markdown sur les 4000 il n’est nécessaire de reconstruire que 10 fichiers HTML. A moins d’avoir vraiment besoin de tout reconstruire, pour une raison technique ou parce que l’on a touché à quelque de chose de commun à tout le site (un template par exemple), la question du temps de génération de la totalité d’un si gros site est caduque. La variable qui influe sur la vitesse de construction est dorénavant la quantité de fichiers modifiés et non plus la quantité de fichiers au total.

A noter que cet avantage n’est pas gratuit, les moteurs de production sont souvent des logiciels assez complexes. L’alternative à priori plus légère et maintenable à cmake et make serait mk.

Si les personnes impliquées dans les communautés des générateurs de site statique avaient identifié des moteurs tels que make comme judicieux pour leurs besoins alors le problème ne ce serait probablement jamais posé.

Dans la pratique une configuration parmi d’autres qui permettrait d’itérer rapidement serait d’avoir une fenêtre vim d’ouverte sur le fichier qui nous intéresse et depuis le dossier dans lequel le makefile se trouve, et une autre fenêtre avec l’aperçu du fichier html. Dans vim on met la configuration “autowrite” (aw de son petit nom) à vraie en faisant set aw et lorsque l’on veut vérifier nos modifs faire :make!. Suffit ensuite de rafraichir la page dans le navigateur.

autowrite permet d’indiquer à vim que vous voulez qu’il enregistre les modifications apportées au lancement de tout un tas de commandes dont make. Voir :help autowrite pour plus d’infos.

Un petit map pour n’avoir à appuyer sur qu’un seul bouton dans vim :

map <F3>:set aw<CR>:make!<CR><CR>
  1. L’utilisation d’éditeurs de texte s’interfaçant bien avec le système permet de tester des bouts de markdown.

Toujours en partant du principe que le besoin principal est d’obtenir un feedback rapide sur ce que l’on a écrit, un éditeur tel que vim permet d’y répondre sans jamais toucher à la génération du site.

Exemple, on a un doute sur la syntaxe des liens (personnellement j’inverse souvent les () et les []), on écrit :

(un lien)[http://katzele.netlib.re]

On teste si ça fonctionne en se mettant sur la ligne et en le filtrant avec lowdown - ou markdown ou pandoc ou… - en lançant :.!lowdown :

<p>(un lien)[<a href="http://katzele.netlib.re">http:&#47;&#47;katzele.netlib.re</a>]</p>

Ah oui non c’est pas ça

[un lien](http://katzele.netlib.re)
<p><a href="http://katzele.netlib.re">un lien</a></p>

mieux !

Si l’on veut tester plusieurs lignes on peut faire une sélection visuelle sur plusieurs lignes ou remplacer . de la commande :.!lowdown par un autre range5.

On peut aussi écrire un heredoc et le filtrer avec sh. En faisant vip puis :!sh (ce qui va écrire :'<,'>!sh dans la ligne de commande, les '<> étant les ranges du début jusqu’à la fin de la sélection visuelle) :

<<. lowdown
| Générateur | Temps (s) |
|---|
| Hugo | 1,6 |
| 11ty | 2 |
| Francium (sans sections) | 6 |
| Francium | 12 |
| Le reste | 20+ |
.

et ça va donner

<p>| Générateur | Temps (s) |
|&#8212;|
| Hugo | 1,6 |
| 11ty | 2 |
| Francium (sans sections) | 6 |
| Francium | 12 |
| Le reste | 20+ |</p>

Ce n’est pas un tableau, réessayons en ajoutant un colonne ligne 2 :

<<. lowdown
| Générateur | Temps (s) |
|---|---|
| Hugo | 1,6 |
| 11ty | 2 |
| Francium (sans sections) | 6 |
| Francium | 12 |
| Le reste | 20+ |
.

et ça donne :

<table>
<thead>
<tr>
<th>Générateur</th>
<th>Temps (s)</th>
</tr>
</thead>

<tbody>
<tr>
<td>Hugo</td>
<td>1,6</td>
</tr>
<tr>
<td>11ty</td>
<td>2</td>
</tr>
<tr>
<td>Francium (sans sections)</td>
<td>6</td>
</tr>
<tr>
<td>Francium</td>
<td>12</td>
</tr>
<tr>
<td>Le reste</td>
<td>20+</td>
</tr>
</tbody>
</table>

Sauf que c’est difficile de dire à l’oeil nu si c’est juste. On peut le piper dans lynx et lui demander de dump le résultat dans vim pour voir ce que ça donne :

<<. lowdown | lynx -stdin --dump
| Générateur | Temps (s) |
|---|---|
| Hugo | 1,6 |
| 11ty | 2 |
| Francium (sans sections) | 6 |
| Francium | 12 |
| Le reste | 20+ |
.

          Générateur        Temps (s)
   Hugo                     1,6
   11ty                     2
   Francium (sans sections) 6
   Francium                 12
   Le reste                 20+

Nickel.

J’imagine qu’il y a pleins d’autres façons d’obtenir du feedback, que ce soit avec vim ou d’autres outils. Vous pouvez écrire au collectif si vous voulez les partager. Ou mieux encore, écrire sur la liste linux du lug.

Mot de la fin

Le but de l’article est de mettre en lumière que pour une situation donnée des cultures informatiques différentes peuvent mener à des lectures radicalement différentes. Ce qui est un problème pour un groupe n’en est pas un pour un autre. Ce qui doit donc être produit pour y remédier par le premier n’a pas à l’être pour l’autre. Ce n’est pas sans conséquence.

Problème ou pas, une autre façon de le voir serait de prendre le problème à l’envers. Plutôt que d’estimer qu’un temps de construction “long” pour un gros site est un problème d’optimisation logiciel, on pourrait estimer que lorsque le temps de construction du site est perçu comme “long” c’est que le site est trop gros.


  1. Peut-être que ça a un gros impact qu’ils ne le soient pas d’ailleurs, je ne sais pas 

  2. Nous utiliserons lowdown à partir d’ici 

  3. Un seul puisque les fichiers markdown du benchmark n’ont pas de “section”. Tout va donc au même endroit dans le layout et un seul fichier de section a besoin d’être créé 

  4. moteur de production en bon français semble-t-il 

  5. . est le range qui veut dire “la ligne sous le curseur”. Voir :help range pour plus d’infos