WordPress est un vraiment chouette logiciel pour créer des sites – blogs, sites vitrines ou de e-commerce, il est suffisamment versatile pour qu’on puisse en faire ce qu’on veut. Il est plutôt efficace, aussi. De base. Une fois ajoutés tous les plugins dont vous aurez envie ou besoin pour différentes fonctionnalités, il est possible qu’il se traîne grave.
Ce n’est pas toujours facile à déterminer d’où viennent les ralentissements, et pour cela, de manière ironique, on va utiliser, entre autres… des plugins ! Des plugins que nous désactiverons une fois les tests terminés.
Le premier, Query Monitor, nous permettra de voir différents aspects d’un chargement de page, dont le temps de génération de la page.
Le deuxième, Code Profiler, nous permettra de voir de manière basique où le temps de génération est passé.
Dans un premier temps on va établir une base de performance : quelle est la performance de WordPress sans plugins ? C’est parti pour tout désactiver, sauf, bien sûr, nos deux plugins de perf.
Ensuite, on teste la homepage, ou une page de catégorie, ou n’importe laquelle. L’important c’est de tester toujours la même, et de tester quelques chargements pour faire une moyenne.
Sans plugin, voici les résultats sur mon site : 0.65s pour rendre la homepage. Le profiler m’informe que le plus clair du temps est passé dans le thème parent, et un peu dans mon thème enfant :
On va ignorer le temps passé dans nos deux plugins de debug, bien sûr.
Maintenant on a une base de comparaison, mais… cette performance c’est une chose, mais là il nous manque nos fonctionnalités ! Chacun·e aura sa propre liste de fonctionnalités non-négociables ou facultatives. Voici la mienne :
- Un antispam pour les commentaires
- Un plugin e-commerce pour ma menuiserie
- Un plugin multi-lingue pour mes posts en anglais
- Une Lightbox pour zoomer les images
- Un plugin social pour les boutons de partage (Vanité quand tu nous tiens…)
- Un plugin de stats pour savoir quelles sont les posts qui intéressent les gens (idem…)
Pour ces différentes fonctionnalités, les choix « évidents » lorsqu’on les cherche (sur Google, dans l’interface d’admin de WordPress) sont :
- Akismet pour le spam
- Woocommerce pour la boutique
- Polylang pour l’internationalisation
- Simple Lightbox pour les images
- Sassy Social Share pour le social
- Jetpack pour les stats (et plus, mais seules les stats m’intéressent)
Et j’en ajoute quelques unes pour les fonctionnalités, comme Quotes for Woocommerce (car ma boutique fait des devis, pas des achats directs), mais je ne m’étendrai pas sur celles-ci.
La partie facile : remplacement des plugins mous
On refait un test ? La homepage charge maintenant en… 2.80s ! Soit quatre fois plus lentement qu’avant… Le profiler pointe les pires coupables :
Partons dans la recherche d’alternatives.
Pour Jetpack : ça dégage, de toutes façons ça fait beaucoup plus de trucs que ce dont j’ai besoin, et ça partage beaucoup trop de données à mon goût. À la place, on met Koko Analytics, super basique, respecteux de la vie privée. Nouveau test : 2.40s, soit 4 dixièmes de seconde d’épluché.
Simple Lightbox, tu prends beaucoup trop de CPU pour faire rien du tout. Ça dégage, on met WP Featherlight à la place et hop : 1.80s (6 dixièmes de moins).
Un autre « fruit bas » ? Sassy Social Share, remplacé par Minimal Share Buttons : certes, c’est moins paramétrable, mais ça me suffit largement, et on arrive à 1.60s (2 dixièmes de moins).
Là, on commence à arriver au bout des alternatives simples : Woocommerce, ça va être dur de le remplacer. Après pas mal de recherche sur internet, il semblerait que je ne sois pas le seul à le trouver mou du genou, et une piste va être… d’ajouter un plugin !? J’ajoute donc Disable Woocommerce Bloat, et on passe à 1.50s (1 dixième de moins). C’est très peu, mais c’est toujours ça.
Comme je ne sais pas trop quoi faire pour les trois grands coupables restants (Woocommerce, Polylang et le thème Boxcard), on va passer à plus bourrin.
Un peu plus loin : le profiling bas niveau
On va faire ça avec XDebug, qui se branche dans PHP et qui a le bon goût de nous filer des dumps cachegrind, un logiciel de profiling que je connais déjà.
On commence par l’installer et le configurer en mode profilage :
sudo apt install php8.1-xdebug sudo cat /etc/php/8.1/fpm/conf.d/20-xdebug.ini zend_extension=xdebug.so xdebug.profiler_enable=1 xdebug.profiler_output_dir=/tmp xdebug.log=/tmp/xdebug.log xdebug.default_enable=1 xdebug.mode=profile xdebug.start_with_request=trigger sudo systemctl restart php8.1-fpm.service
Cela va nous permettre de déclencher un profil d’une page en ajoutant un paramètre à la requête : ?XDEBUG_TRIGGER=yes
Une fois la requête tracée, on récupère un fichier /tmp/cachegrind.out.437810 sur le serveur et on l’ouvre avec KCacheGrind. Voici ce qu’on en sort :
Par défaut, la liste des fonctions à gauche est triée par « temps inclusif ». C’est à dire le temps passé dans la fonction en comptant celui passé dans celles qu’elle appelle. Il est souvent plus intéressant de trier par « temps personnel », le temps passé dans une fonction sans compter celui passé dans celles qu’elle appelle.
Une fois trié comme ça, on voit que… qu’on passe le plus clair de notre temps… à faire des traductions ?!
Après pas mal de recherches sur le sujet, il apparaît que WordPress a fait le choix de shipper une implémentation en PHP de gettext, basée sur POMO. C’est très bien, mais c’est lent. Il y a des tickets sur le sujet. On y découvre l’existence du plugin WP Performance Pack, qui promet, entre autres, une réimplémentation de la librairie d’internationalisation utilisant la lib gettext native de l’hôte lorsqu’elle est disponible.
C’est parti pour tester ça : on installe le plugin, on sudo apt install php8.1-gettext
, on active la localisation, gettext natif et l’alternative MO reader dans les réglages du plugin :
Et on reteste : Homepage chargée en 0.93s ! (6 dixièmes de moins). Le profiler nous indique maintenant que WP Performance Pack prend du temps… Mais il en fait tellement gagner ailleurs qu’il vaut absolument le coup !
Un nouveau profile XDebug nous montre qu’on passe effectivement largement moins de temps à traduire, et que l’implémentation native est bien en route :
Cependant, WP Performance Pack fait plein d’autres trucs dont je n’ai pas besoin, et de plus il n’a pas l’air maintenu. Quelques heures de boulot plus tard, un nouveau plugin est né : Native Gettext for WordPress ! Ce plugin fait le minimum du minimum, partant du principe que le code le plus rapide est celui qui n’est pas exécuté ni même écrit.
Mise à jour, avril 2024: WordPress 6.5 a intégré Performant Translations, un autre plugin visant à accélérer les traductions. Il est vagument plus lent que Native Gettext, mais dans des proportions négligeables, ce qui signe l’arrêt de la maintenance de Native Gettext.
Il reste, j’en suis sûr, des choses à gratter dans Woocommerce, mais il faudrait se plonger dans le code. J’ai déjà pushé un petit fix qui fait gagner quelques dizaines de millisecondes, j’en ai un autre sous le coude qui j’espère sera accepté aussi.
Il reste certainement du gain sur Polylang, mais ici aussi, l’effort sera un peu haut : les auteur·rice·s du plugin semblent au courant des questions de performance.
Pour le fun, on va ajouter Redis Object Cache.
Ensuite, on peut fouiller dans les dumps Cachegrind et, partant du principe que le code le plus rapide, c’est celui qu’on n’appelle pas, trouver des candidats à la suppression. C’est au moins aussi amusant que Wordle, et ça permet potentiellement de contribuer un peu aux logiciels libres qu’on utilise ! Quelques exemples :
- Micro-optimisation de Redis Object Cache
- Micro-optimisation du thème Boxcard, chargeant des trucs backend-only même sur le front
Une dernière chose pour la page d’accueil, et toutes les pages de listes. Les « extraits » (résumés) de posts peuvent être spécifiés manuellement dans l’interface d’édition. Lorsqu’ils ne le sont pas, le début de chaque post est extrait au runtime, coupé à 55 mots, et filtré de diverses manières pour ne donner que du texte. Comme je suis un feignant, aucun de mes posts n’a d’extrait manuellement spécifié. Du coup, le travail de nettoyage du début du post est refait à chaque affichage, et (on ne le voit pas sur les screenshots ci-dessus), cela prend un certain temps. Pour arranger cela, j’ai installé le plugin WP Excerpt Generator, et j’ai auto-généré tous les extraits pour enregistrement en base. Cela fait gagner peu (~50-100ms), mais c’est toujours bon à prendre !
Étape 3 : la chaîne HTTP
Jusqu’ici on s’est concentré sur la génération de la page. Mais ce n’est pas tout ce qui fait la performance ! Lorsqu’un·e internaute demande une page du site, la page est rendue et renvoyée, mais vient ensuite le reste : les ressources, CSS, Javascript, images, etc.
Chargeons la homepage en regardant l’onglet Network des Developers Tools de Firefox :
On voit la requête à /wordpress/ qui prend 1.389s. C’est plus que les précédents chiffres remontés par Query Monitor, car Query Monitor nous donne le temps de génération de la page côté serveur. Ici, Firefox nous donne le temps total entre la requête DNS, la connexion HTTPS, la génération et l’envoi des données. Elle est suivie de nombreuses requêtes à des scripts JS et du CSS. Essayons de grouper les CSS et les Javascript ensemble, en ajoutant le plugin Autoptimize.
Le temps total est à peine plus bas, mais cela fait moins de requêtes. C’est mieux.
Étape 4 : la stack serveur
On a déjà bien avancé, mais pour finir, il reste plusieurs pistes de gains, toutes côté serveur :
- HTTP2 pour faire moins de connexions
- Compression des ressources statiques
- Envoi des images au format webp pour les clients qui le gèrent
- Activation d’un opcache pour PHP
- Réglages SSL
- Insertion d’un cache extrêmement efficace, Varnish, pour tout simplement sauter toute la partie génération sur laquelle on vient de travailler.
Cela fait l’objet de deux autres posts :
mais en attendant, voici le résultat pour un·e internaute lambda à la fin :
Merci pour ce partage <3
Le css et le critical css est intéressant aussi comme piste à creuser.. Souvent mal optimisé dans les plugins… et chargé plusieurs fois pour le JS. Bref bcp de choses chargés inutilement. Qu'en penses-tu ?
Pour le css en "rapide" j'ai testé récemment Purifycss dot online, qui permet de checker le css chargé inutilement, et de te sortir les css sans ce qui n'est pas utilisé, et pegasaas dot com/critical-path-css-generator/ pour générer le css critique.
Les résultats en terme de performance étaient considérables, mais j'avais pas mal de soucis d'affichage, donc après il est parfois nécessaire d'approfondir et de customiser les fichiers fournis (et de customiser les chargements, à minima en fonction des types de page chargés)
Merci encore
Oui pareil… J’en n’ai pas encore parlé parce que je me sens pas assez compétent sur le sujet. Clairement l’analyse des morceaux utilisés ou non est une vaste tâche pour ne rien péter.
D’autant qu’il faut le faire sur chaque page différente…
Mais j’y viendrai peut-être !