vendredi 25 septembre 2026

À propos

Contact

Tests

Une suite de tests trois fois plus lente après une montée de version de PHP

Un ralentissement soudain après une mise à jour de PHP peut venir de l'interpréteur, d'une extension chargée, ou d'une dépendance de test elle-même. Comment trier entre ces trois hypothèses.

Par Clément Hadrot • 4 août 2026 • 4 min de lecture • Aucun commentaire
Une suite de tests trois fois plus lente après une montée de version de PHP

Symptôme. Après la montée de version d’un serveur d’intégration continue interne de PHP 8.1 vers PHP 8.3, une suite de 640 tests PHPUnit, jusque-là stable autour de 90 secondes, s’est mise à durer près de 280 secondes, sans qu’aucune ligne de code du projet n’ait changé entre les deux exécutions. Trois hypothèses étaient plausibles : un ralentissement propre à l’interpréteur PHP lui-même, une extension chargée par défaut sur la nouvelle image qui ne l’était pas sur l’ancienne, ou une dépendance de test dont le comportement change selon la version de PHP exécutée.

Diagnostic : isoler une variable à la fois

La première erreur à éviter est de changer plusieurs paramètres en même temps en espérant deviner juste. On commence par exécuter un script minimal, sans aucune dépendance du projet, purement arithmétique, sur les deux versions de PHP pour écarter ou confirmer l’hypothèse de l’interpréteur :

php -r '
$debut = microtime(true);
for ($i = 0; $i < 50000000; $i++) { $x = $i * 2; }
echo microtime(true) - $debut, PHP_EOL;
'
// PHP 8.1 : 0.89 s
// PHP 8.3 : 0.81 s (légèrement plus rapide, comme attendu du moteur seul)

L'interpréteur seul est donc écarté : PHP 8.3 est même un peu plus rapide que PHP 8.1 sur du calcul pur, conformément aux gains habituels annoncés d'une version à l'autre.

Deuxième hypothèse : une extension chargée par défaut

On compare la liste des extensions actives sur les deux environnements :

php -m > extensions-8.1.txt
# sur le nouveau serveur
php -m > extensions-8.3.txt
diff extensions-8.1.txt extensions-8.3.txt

Le diff révèle que Xdebug, absent de l'image PHP 8.1 précédente, est chargé par défaut sur la nouvelle image PHP 8.3, y compris en mode develop qui ralentit sensiblement l'exécution même sans collecte de couverture activée. C'est une cause fréquente et facile à corriger :

php -v
# PHP 8.3.9 (cli) (built: ...)
#     with Xdebug v3.3.1, Copyright (c) 2002-2024, by Derick Rethans

# désactiver Xdebug pour l'exécution des tests quand la couverture n'est pas nécessaire
php -d xdebug.mode=off vendor/bin/phpunit
L'essentiel à retenir : Isoler chaque hypothèse une par une, jamais toutes en même temps ; Un profileur donne la réponse en quelques minutes ; La dépendance de test est souvent la coupable oubliée

Cette seule correction a ramené la durée de 280 à environ 165 secondes, un progrès net mais insuffisant pour retrouver les 90 secondes d'origine : une troisième cause restait donc à identifier.

Troisième hypothèse : une dépendance de test dont le comportement a changé

Le profilage avec Xdebug lui-même, cette fois activé volontairement en mode profile sur un sous-ensemble réduit de la suite, a mis en évidence qu'une bibliothèque tierce de génération de données factices (fzaninotto/faker, alors non maintenue) instanciait un nouveau générateur aléatoire à chaque appel plutôt qu'une seule fois, un comportement resté inaperçu en PHP 8.1 mais dont le coût s'est révélé nettement plus élevé sous PHP 8.3 à cause d'un changement interne dans la génération de nombres aléatoires cryptographiquement sûrs utilisée par cette bibliothèque.

protected function setUp(): void {
    parent::setUp();
    $this->faker = \Faker\Factory::create( 'fr_FR' ); // recréé à chaque test, coûteux
}

Le correctif a consisté à instancier ce générateur une seule fois pour l'ensemble de la suite, via une propriété statique partagée entre les tests, et à migrer vers un remplacement activement maintenu :

private static ?\Faker\Generator $faker = null;

protected function setUp(): void {
    parent::setUp();
    if ( self::$faker === null ) {
        self::$faker = \Faker\Factory::create( 'fr_FR' );
    }
}

Prévention pour la prochaine montée de version

  • Comparer systématiquement la liste des extensions actives avant et après toute montée de version d'infrastructure, pas seulement la version de PHP annoncée.
  • Désactiver explicitement Xdebug en dehors des exécutions qui en ont réellement besoin, via une variable d'environnement dédiée plutôt qu'une configuration implicite de l'image.
  • Garder un œil sur les dépendances de test elles-mêmes lors d'une montée de version, elles sont aussi susceptibles de changer de comportement que le code applicatif.

En résumé

Ce billet ne traite pas du JIT de PHP, une piste que nous avons envisagée en premier lieu par réflexe mais qui s'est révélée hors sujet dans ce cas précis, le JIT n'étant de toute façon pas activé par défaut sur les images CLI standards utilisées ici. Le ralentissement provenait d'une combinaison de deux causes indépendantes, une configuration d'image et une dépendance de test mal utilisée, ce qui rappelle qu'un diagnostic de performance gagne toujours à isoler chaque variable plutôt qu'à incriminer la cause la plus visible en premier.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi