À cette échelle, le chargement des classes, la résolution des dépendances et les vérifications d’I/O consomment une part mesurable du budget de latence.
L’OPcache classique réduit déjà le coût de compilation en conservant le bytecode PHP en mémoire partagée. Le preloading va plus loin: il exécute un script au démarrage de PHP-FPM ou d’Apache, résout les classes et fonctions ciblées, puis les conserve dans la mémoire du processus. À chaque requête, le moteur ne repart donc pas de l’autoloader pour reconstruire le même graphe de dépendances.
L’impact de l’OPcache preloading sur les performances PHP est réel, mais il n’est ni universel ni proportionnel à la taille du projet. Sur une application dominée par les accès réseau, les requêtes SQL ou des traitements lourds, le gain lié au chargement des classes devient marginal. Sur un noyau applicatif rapide, il peut réduire directement la latence incompressible.
Mécanisme interne: au-delà du cache de bytecode classique
L’OPcache standard agit principalement sur la compilation. Lorsqu’un fichier PHP est exécuté, le moteur peut conserver son bytecode en mémoire partagée au lieu de relire et recompiler le fichier à chaque requête. La compilation disparaît du chemin critique, mais le reste du cycle de chargement ne disparaît pas pour autant.
Une application moderne ne se résume pas à un fichier PHP compilé. Un framework comme Symfony ou Laravel mobilise un ensemble de classes, d’interfaces, de traits, de fichiers de configuration et de dépendances. Même lorsque le bytecode est déjà disponible, l’application doit encore:
- demander à l’autoloader de résoudre des noms de classes;
- vérifier l’existence de fichiers et de chemins;
- charger les dépendances qui ne sont pas encore présentes;
- établir les relations entre classes, interfaces et traits;
- exécuter le code nécessaire à l’initialisation du noyau applicatif.
Le coût n’est donc pas limité à la compilation. L’autoloading ajoute des appels, des résolutions de chemins et des vérifications sur le système de fichiers. Ces opérations sont souvent faibles prises isolément. Elles deviennent significatives lorsqu’elles se répètent sur chaque requête et que le reste de la réponse est déjà optimisé.
Le preloading introduit dans PHP 7.4 un autre modèle d’exécution. Le serveur charge et compile les scripts ciblés avant de traiter les requêtes. Les fonctions et les classes préchargées deviennent disponibles globalement, à la manière des éléments natifs du langage.
La différence avec un simple cache de fichiers tient à la résolution des dépendances. L’OPcache conserve le résultat de la compilation. Le preloading prépare aussi une partie de l’environnement d’exécution: les classes sont déjà connues du moteur, leurs dépendances ont été parcourues et l’autoloader n’a plus à répéter ce travail pour ces éléments.
L’OPcache retire la compilation du chemin critique. Le preloading retire aussi une partie de la résolution des classes.
Cette distinction explique pourquoi l’OPcache preloading n’a pas le même intérêt selon le profil de l’application. Si la compilation représente l’essentiel du coût, l’OPcache standard suffit déjà à traiter le problème principal. Si l’application est composée d’un grand nombre de classes chargées à chaque requête, le preloading attaque une couche supplémentaire.
Ce qui est réellement mis en mémoire
Le script de preloading n’est pas un cache applicatif généraliste. Il ne stocke pas les réponses HTTP, les résultats SQL ou les objets construits pendant une requête. Il prépare le code PHP lui-même.
Le processus démarre. PHP exécute le script indiqué par opcache.preload. Ce script charge les fichiers et les classes choisis. Le moteur conserve ensuite ces structures dans la mémoire partagée ou dans l’espace mémoire associé au processus, selon le fonctionnement interne de la version et de la configuration PHP.
Le résultat dépend directement de la qualité du périmètre sélectionné. Un préchargement trop limité laisse une grande partie du graphe de dépendances à l’autoloader. Un préchargement trop large augmente le temps de démarrage et l’empreinte mémoire sans garantir un gain sur les chemins réellement utilisés.
La cible n’est donc pas la totalité du répertoire vendor. La cible est le code stable, fréquent et coûteux à résoudre. Dans une application Symfony, cela peut concerner le noyau du framework, les composants utilisés sur la majorité des routes et certaines classes métier. Dans une application Laravel, le raisonnement est similaire: le code partagé entre les requêtes a davantage de valeur que les branches rarement exécutées.
Le rôle du preloading dans l’élimination de l’autoloading
L’autoloader de Composer est une pièce efficace, mais il intervient tout de même dans le chemin d’exécution. Son optimisation — notamment avec l’optimisation de l’autoload en production — ne supprime pas toutes les opérations. Elle réduit les recherches, améliore la résolution et limite les accès inutiles. Elle ne transforme pas automatiquement chaque classe en entité déjà disponible dans le moteur PHP.
Le preloading agit avant la requête. Les classes qu’il a chargées sont déjà déclarées lorsque le contrôleur, le middleware ou le service applicatif commence son travail. Il n’y a plus de résolution dynamique pour ces classes.
Le gain est particulièrement visible dans les scénarios où:
- le temps de réponse applicatif est inférieur à 100 ms;
- le framework instancie de nombreuses classes par requête;
- les routes partagent un noyau de dépendances stable;
- le coût SQL et réseau est déjà faible;
- le nombre de workers PHP-FPM est suffisamment élevé pour rendre l’empreinte mémoire mesurable.
Dans ces conditions, un surcoût d’autoloading de 10 à 16 ms peut représenter une fraction substantielle de la latence totale. La suppression de ce coût ne rend pas l’application instantanée. Elle réduit une composante précise du temps de traitement.
Le raisonnement change complètement avec une requête à 1 seconde. Si la majorité de cette seconde est consommée par une base de données, une API distante, un calcul métier ou une sérialisation volumineuse, le preloading ne traite pas le goulet d’étranglement. Le gain relatif peut alors rester inférieur à 1 %.
Le bon périmètre de préchargement
Le choix des fichiers est le point technique le plus sensible. La question n’est pas de savoir si une classe peut être préchargée. Elle est de savoir si elle doit l’être.
Un bon périmètre possède quatre propriétés:
- il est utilisé par une part importante des requêtes;
- son code change peu entre deux déploiements;
- ses dépendances sont compatibles avec le chargement au démarrage;
- son empreinte mémoire reste acceptable pour chaque instance PHP.
Le code métier très instable est un mauvais candidat. Toute modification d’un fichier préchargé exige un redémarrage du service PHP pour que le nouveau code soit pris en compte. En production, ce redémarrage doit être intégré au déploiement. En développement, cette contrainte rend le mécanisme peu pratique.
Les classes liées à une route exceptionnelle, à un module rarement appelé ou à une fonctionnalité administrative ne doivent généralement pas être prioritaires. Leur préchargement immobilise de la mémoire pour un gain nul sur le trafic courant.
Le découpage doit aussi respecter les dépendances. Charger une classe sans ses parents, interfaces ou traits peut produire un script incomplet ou un échec au démarrage. Le script de preloading doit donc refléter la structure réelle du graphe de classes, pas une simple liste arbitraire de fichiers.
Configuration et mise en œuvre via php.ini
La configuration repose sur deux directives principales:
opcache.preloaddésigne le script PHP exécuté au démarrage;opcache.preload_userdéfinit l’utilisateur système qui exécute ce script.
La première directive pointe vers un fichier accessible par le processus PHP. La seconde est indispensable dans un environnement où le service ne doit pas exécuter le chargement avec des privilèges excessifs.
Un exemple de configuration conceptuelle peut tenir en deux lignes dans php.ini: opcache.preload=/chemin/vers/preload.php et opcache.preload_user=www-data. Les valeurs exactes dépendent de l’utilisateur du service PHP-FPM, de la distribution et de l’arborescence du déploiement.
Le script de preloading doit ensuite charger le code retenu. La méthode utilisée doit être cohérente avec le chargeur de classes de l’application et avec l’organisation du projet. Dans un projet Composer, le script peut s’appuyer sur l’autoloader disponible, à condition que les classes ciblées soient effectivement résolues pendant cette phase.
La configuration ne se limite pas à activer une directive. Il faut contrôler plusieurs points opérationnels:
1. Le chemin absolu du script.
Un chemin relatif ou dépendant du répertoire courant rend le démarrage fragile. PHP-FPM doit retrouver le fichier indépendamment de la manière dont le service est lancé.
2. Les permissions système.
L’utilisateur défini par opcache.preload_user doit pouvoir lire le code et ses dépendances. Il ne doit pas disposer de privilèges inutiles sur le système.
3. Le cycle de redémarrage.
Le preloading est évalué au démarrage de PHP-FPM ou d’Apache. Une modification du code préchargé sans redémarrage ne recharge pas ce code à chaud.
4. La séparation des environnements.
Le script de production ne doit pas être appliqué mécaniquement au développement. Le rechargement fréquent du code et les outils de débogage sont incompatibles avec un modèle où les classes restent préchargées jusqu’au redémarrage.
5. La surveillance de la mémoire.
Chaque worker, chaque pool et chaque conteneur doit être dimensionné avec l’empreinte du code préchargé. Le gain de CPU ne justifie pas un épuisement de la mémoire disponible.
Déploiement et invalidation
Le preloading modifie la procédure de déploiement. Avec un cache de bytecode classique, l’invalidation peut souvent être gérée par la configuration d’OPcache ou par le remplacement contrôlé des fichiers. Avec le preloading, le processus PHP conserve les classes préparées au démarrage.
Le déploiement doit donc suivre une séquence stricte:
- publier la nouvelle version du code;
- vérifier que le script de preloading pointe vers la bonne version;
- redémarrer PHP-FPM ou Apache;
- contrôler que les workers ont bien repris le nouveau code;
- mesurer la latence après le redémarrage.
Un redémarrage partiel peut créer une situation incohérente entre workers: certains exécutent l’ancienne version préchargée, d’autres la nouvelle. La stratégie de renouvellement des processus doit éviter cette divergence. Dans un environnement conteneurisé, le remplacement de l’instance est souvent plus lisible qu’une modification en place, mais le principe reste le même: le code préchargé est lié au cycle de vie du processus.
Impact réel sur les performances: analyse des gains de latence
Le benchmark utile ne mesure pas seulement le temps moyen. Une moyenne peut masquer une distribution instable, des cold starts ou une hausse de la latence aux percentiles élevés.
Pour évaluer l’impact de l’OPcache preloading sur une application PHP, il faut comparer au minimum:
- le temps de réponse médian;
- les percentiles élevés, notamment P95 et P99 si les données sont disponibles;
- le temps CPU consommé par requête;
- le nombre d’opérations d’autoloading;
- le temps de démarrage de PHP-FPM;
- l’empreinte mémoire des workers;
- la latence avant et après redémarrage.
Le protocole doit isoler le coût traité par le preloading. Si une requête dépend d’une base de données lente, le résultat sera dominé par l’I/O SQL. Si elle appelle un service distant, le réseau écrasera le signal. Le benchmark doit donc inclure des routes représentatives mais aussi des chemins courts, afin d’observer la part de l’initialisation PHP dans la latence.
Le chiffre de 10 à 16 ms d’autoloading économisé correspond à un ordre de grandeur observé dans certaines applications Symfony ou Shopware. Il ne constitue pas une promesse pour toutes les applications PHP. La taille du graphe de classes, la stratégie d’autoload Composer, la version de PHP, la configuration d’OPcache et la charge externe modifient le résultat.
| Profil de requête | Effet attendu du preloading | Explication |
|---|---|---|
| Réponse inférieure à 100 ms, noyau PHP dense | Potentiellement significatif | L’autoloading occupe une part visible du budget de latence |
| Réponse dominée par une requête SQL | Faible à marginal | Le temps de base de données reste le goulet d’étranglement |
| Appel à une API distante | Faible | Le réseau masque le gain obtenu au démarrage de la requête |
| Traitement PHP intensif | Variable | Le preloading réduit l’initialisation, pas le coût algorithmique |
| Application à processus persistant | Nul ou non pertinent | Le cycle de vie mémoire est déjà géré autrement |
| Code très fréquemment modifié | Coût opérationnel élevé | Chaque changement impose un redémarrage pour être pris en compte |
Le preloading doit donc être évalué comme une optimisation de la latence d’initialisation, pas comme une accélération générale du moteur PHP. Il ne rend pas une requête SQL mieux indexée. Il ne réduit pas la taille d’un objet sérialisé. Il ne corrige pas un algorithme en complexité quadratique. Il supprime une étape répétitive et déterministe du chargement applicatif.
Mesurer avant de précharger
La première mesure à collecter est le temps réellement consacré à l’autoloading. Sans cette donnée, le script de preloading devient une hypothèse difficile à auditer.
Le diagnostic peut s’appuyer sur le profilage applicatif, les traces de performance et les métriques PHP-FPM. Le but est de distinguer:
- la compilation;
- la résolution des classes;
- l’initialisation du framework;
- la construction du conteneur de services;
- les accès au système de fichiers;
- les requêtes SQL;
- les appels réseau;
- le traitement métier.
Cette séparation est déterminante. Un gain de quelques millisecondes sur l’autoloading peut être noyé dans une requête qui exécute plusieurs appels externes. À l’inverse, sur une API interne à faible latence, la même réduction peut améliorer sensiblement le débit et les percentiles.
Le benchmark doit également être exécuté avec une charge représentative. Une mesure sur un seul processus PHP ne reflète pas le comportement d’un pool PHP-FPM complet. Le nombre de workers, la pression mémoire et la fréquence des redémarrages peuvent transformer un gain CPU local en coût système global.
Le preloading est rentable quand l’autoloading est mesuré comme un goulot. Sans mesure, on ne fait qu’immobiliser du code en mémoire.
Limites opérationnelles et incompatibilités avec les environnements persistants
Le preloading est conçu pour un modèle où le serveur PHP démarre des processus qui traitent ensuite des requêtes. Ce modèle reste courant avec PHP-FPM et Apache. Il ne s’applique pas de la même manière aux environnements qui conservent déjà l’application en mémoire entre les requêtes.
Laravel Octane, RoadRunner, FrankenPHP et AWS Lambda gèrent le cycle de vie de la mémoire selon des mécanismes différents. Dans ces environnements persistants ou sans serveur, les classes et une partie de l’état applicatif sont déjà maintenus selon le runtime utilisé. Le preloading n’apporte donc pas de gain net à présenter comme acquis.
Le risque n’est pas uniquement la performance nulle. Il est aussi architectural. Ajouter le preloading à un runtime dont le cycle de vie est déjà persistant complexifie la chaîne de démarrage, la gestion des versions et le diagnostic des états mémoire sans réduire nécessairement la latence.
Mémoire, workers et densité
Le gain d’autoloading se paie par une consommation mémoire et par un temps de démarrage supérieur. Le script est exécuté avant le traitement des requêtes. Un redémarrage PHP-FPM plus lourd est donc attendu, surtout si le périmètre préchargé contient beaucoup de classes.
Cette contrainte doit être rapprochée du dimensionnement du pool:
- un grand nombre de workers augmente la capacité de traitement;
- chaque worker consomme de la mémoire;
- le code préchargé augmente l’empreinte de l’environnement PHP;
- une limite mémoire trop serrée peut provoquer des évictions, des redémarrages ou une dégradation du débit.
Le bon indicateur n’est pas seulement la mémoire du processus au démarrage. Il faut observer la consommation pendant une charge représentative, avec les extensions actives, le framework initialisé et les requêtes habituelles. Une optimisation qui réduit le temps CPU mais force une diminution trop forte du nombre de workers peut dégrader le débit global.
Le preloading doit aussi rester compatible avec les extensions PHP, les mécanismes de déploiement et les pratiques de sécurité. Le script de démarrage devient une partie sensible de l’infrastructure. Il doit être versionné, testé et contrôlé comme le reste du code de production.
Code dynamique et rechargement
Le modèle du preloading suppose que les classes chargées au démarrage restent valides pendant la durée de vie du processus. Les architectures qui modifient dynamiquement le code ou qui dépendent fortement d’un rechargement à chaud sont donc de mauvais candidats.
Le changement de code préchargé exige un redémarrage du service PHP. Il ne s’agit pas d’une limitation secondaire: c’est la conséquence directe du mécanisme. La mémoire du processus contient la version chargée au démarrage. Modifier le fichier sur disque ne remplace pas automatiquement les structures déjà présentes dans le moteur.
En production, cette règle est gérable avec un déploiement atomique et un renouvellement contrôlé des workers. En développement, elle ralentit la boucle de modification et de test. La configuration de preloading doit donc rester réservée à l’environnement où elle produit un bénéfice mesurable.
Preloading, OPcache et optimisation globale: quel ordre de priorité?
Le preloading ne remplace pas les optimisations fondamentales. Une application PHP mal configurée ne devient pas performante par l’ajout d’un script de démarrage.
L’ordre rationnel est plus strict:
1. Réduire les requêtes SQL inutiles et corriger les plans d’exécution.
Une base de données mal indexée domine rapidement plusieurs millisecondes d’autoloading.
2. Optimiser l’autoload Composer pour la production.
Le preloading intervient après cette étape. Il ne doit pas compenser une résolution de classes mal configurée.
3. Activer et vérifier l’OPcache standard.
Le cache de bytecode constitue la base. Le preloading ajoute une optimisation au-dessus de cette couche.
4. Mesurer l’initialisation du framework.
Il faut identifier les classes effectivement communes aux requêtes et leur coût de chargement.
5. Précharger un périmètre réduit et stable.
Le script doit cibler le code à forte fréquence d’utilisation, pas l’ensemble du projet par réflexe.
6. Comparer les résultats sous charge.
Les métriques doivent inclure la latence, le CPU, la mémoire et le comportement du pool PHP-FPM.
Le preloading peut aussi entrer en conflit avec une stratégie de réduction de l’empreinte mémoire. Dans une architecture disposant de nombreuses instances peu chargées, le coût de conserver un grand ensemble de classes en mémoire sur chaque instance peut dépasser le bénéfice CPU. À l’inverse, une application monolithique avec un trafic soutenu et un noyau de dépendances stable présente un terrain plus favorable.
La décision dépend du ratio entre le coût d’initialisation et le coût du traitement métier. Le framework n’est pas le problème par définition. Ce qui compte est la proportion de temps consacrée au chargement de ses composants dans le profil réel des requêtes.
Verdict: à utiliser en production, mais uniquement sous mesure
L’OPcache preloading est une optimisation de production pertinente pour les applications PHP-FPM ou Apache rapides, fortement composées et dominées par le coût d’autoloading. PHP 7.4 l’a introduit comme une extension logique de l’OPcache: d’abord conserver le bytecode, ensuite préparer les classes et leurs dépendances avant la première requête.
Le mécanisme ne doit pas être activé par simple conformité à une recette de performance. Il augmente le temps de démarrage, impose un redémarrage après modification du code préchargé et accroît l’empreinte mémoire. Son gain devient faible dès que la latence est absorbée par SQL, le réseau ou un traitement métier lourd. Il n’apporte pas de gain net à présenter comme acquis avec Laravel Octane, RoadRunner, FrankenPHP ou AWS Lambda.
Verdict binaire: à utiliser en production si l’autoloading pèse une part mesurée de la latence et si le cycle de redémarrage est maîtrisé. À ne pas utiliser si le profilage ne montre aucun coût significatif de chargement ou si l’application fonctionne déjà dans un runtime persistant.




