Améliorer les performances d'une application PHP
Publié par Sebastien Dudek il y a 6 mois
Le cache d'op-codes
Le cache d'op-codes est un moyen immédiat d'optimiser les performances globales d'une application. Il est adapté à la plupart des développements PHP et intervient dans la couche "basse" de PHP, c'est à dire entre la lecture du code PHP et son exécution.
Avant d'aborder le fonctionnement d'un cache d'op-codes, il est important d'assimiler la manière dont votre code PHP est exécuté.
Entre votre clic de souris et l'exécution du code proprement dit, plusieurs événements s'enchaînent :
- Tout d'abord, le code PHP est "parsé", c'est à dire lu par un programme (parseur) qui est capable de séparer les blocs et les instructions que le développeur a saisi avant de les transmettre au compilateur.
- Une fois lu par le parseur, le code PHP est transmis au compilateur qui transforme le code PHP, lisible par nous les humains, en code machine (instructions très proches des instructions du processeur) lisible par l'ordinateur, par l'intermédiaire d'un troisième petit programme : l'exécuteur.
- Une fois compilé, il s'agit d'exécuter le code afin de fournir le résultat attendu. Cette opération est assurée par l'exécuteur.
Qu'est-ce qu'un op-code ?
Op-code signifie "opération-code". A l'issue d'une compilation, le code PHP est transformé en listes d'instructions élémentaires destinées à être exécutées. Ces listes d'instructions sont précisément appelées "tableaux d'op-codes". En d'autres termes, les op-codes sont un intermédiaire entre le code PHP que nous connaissons et les instructions du processeur, bien que plus proche des instructions processeur que du code PHP original.
Comment fonctionne le cache d'op-codes ?
Le rôle du cache d'op-codes est d'optimiser ce processus en factorisant les étapes redondantes : le parsing et la compilation. Tant que le code PHP original n'est pas modifié par un développeur, le parseur et le compilateur produiront les mêmes tableaux d'op-codes. Plutôt que de les régénérer à chaque fois, le cache d'op-codes stocke les op-codes en mémoire vive et les restitue à partir de la deuxième exécution.
Ainsi, la première exécution de votre programme sera légèrement plus lente du fait du stockage en mémoire des op-codes, et c'est à partir de la deuxième exécution que les performances se font sentir.
En moyenne, les applications PHP augmentent de 4 fois leur vitesse rien qu'avec le cache d'op-codes. Cette valeur est cependant très dépendante des opérations effectuées par votre application : les accès en base de données ou les services web ne seront pas significativement accélérés par le cache d'op-codes, en revanche les performances de tous les algorithmes et les calculs effectués en PHP seront concernés par l'accélération.
APC comme cache d'op-codes
Il existe plusieurs applications permettant d'accélérer le code PHP. Citons eAccelerator, Zend Optimizer et le fameux APC qui sera vraissemblement intégré à PHP 6 dans un avenir proche.
APC (Alternative PHP Cache) est un cache d'op-codes performant et fiable qui fournit également des fonctions pratiques pour mettre en oeuvre un cache mémoire. Grâce à cette extension, il est possible non seulement d'accélérer votre code PHP sans aucun paramétrage, mais aussi de permettre l'écriture et la lecture d'informations persistantes en mémoire RAM, ce qui sera beaucoup plus efficace que dans un fichier situé sur le disque dur.
Nous verrons dans la partie "Cache Partiel" comment utiliser APC pour mettre en oeuvre un moteur de cache vraiment efficace.
Optimiser une application pour le cache d'op-codes
L'architecture de votre application peut influencer significativement les performances du cache d'op-codes. Si ce système permet d'accélérer l'exécution du code dans la plupart des cas, il peut aussi, avec une mauvaise architecture, en réduire les performances.
Tout d'abord, il faut considérer que la mémoire vive (RAM) de votre ordinateur reste limitée en taille. En cas de dépassement, vous aurez à faire à du "swap", c'est à dire que le disque dur prendra le relais sur la mémoire vive si cette dernière subit un dépassement de capacité. Ce cas se produira si votre application comporte un nombre très important de fichiers PHP, car chaque fichier génère son lot d'op-codes qui prennent de la place dans la mémoire.
Notre premier conseil : factorisez votre code PHP pour réduire la taille des op-codes et utilisez un nombre limité de fichiers. Evitez par exemple de créer un fichier PHP à chaque fois que vous faites une page, privilégiez l'utilisation d'un moteur de templates pour générer vos pages et factoriser les éléments redondants.
D'autre part, nous avons vu que l'opération initiale, qui consiste à créer les op-codes et à les mettre en mémoire, est plus coûteuse qu'une requête hors cache d'op-codes. PHP se base sur la date de dernière modification de vos fichiers pour savoir s'ils ont été modifiés ou non. Ce cas reste assez rare, mais si pour une raison ou pour une autre cette date de modification est souvent altérée, alors le cache d'op-codes peut ralentir votre application.
Le cache de pages
Toujours dans la lignée des solutions de cache, le cache de pages est une des pratiques les plus employées pour améliorer l'accès à des pages statiques ou semi-statiques.
Ce cache est dit de "haut niveau" car il agit une fois que les pages sont entièrement créées. Son principe est simple : votre page HTML est créée une première fois puis "mise en cache", c'est à dire stockée quelque part sur votre disque dur ou dans la mémoire, afin d'être restituée par la suite sans avoir à reconstruire la page.
Solutions existantes pour le cache de pages
Etant donné que ce cache est de "haut niveau", il peut être assuré non seulement par PHP, par exemple à l'aide d'un programme de votre choix ou un moteur de templates comme Smarty, mais également par le serveur HTTP. L'avantage de cette dernière solution est d'être "native", c'est à dire que le moteur de cache est compilé, donc plus performant que s'il était écrit avec PHP. Apache 2 par exemple propose un module, mod_cache, qui permet de mettre en cache des pages automatiquement. Les désavantages par rapport à PHP sont la rigidité de la solution et la nécessité de disposer de ce module spécifique d'Apache, paramétré correctement.
En PHP, certains moteurs de templates - nous avons cité Smarty mais il en existe d'autres - permettent de mettre en oeuvre un cache de pages. Vous pouvez aussi utiliser des extensions ou composants dédiés, tels que jpCache ou APC, mais méfiez-vous de ce dernier qui donne accès à la mémoire et donc doit être manipulé avec précaution. Créer son propre cache de pages est par ailleurs facile, comme nous le verrons par la suite.
Ne pas abuser du cache !
Ne considérez pas le cache de pages et le cache partiel comme un palliatif à un défaut de performances significatif de votre application. Souvent on entend "l'application rame, il n'y a qu'une solution, mettre du cache !". Dans 98 pour cent des cas, cette affirmation est fausse. Votre algorithme doit être amélioré et remanié pour être raisonnablement performant. Le cache est une couche de code supplémentaire qui permet de réduire les ressources consommées en cas d'appels fréquents d'une page ou d'une fonctionnalité. L'expérience montre qu'un mauvais algorithme avec du cache ne résoud jamais complètement votre problème, surtout si l'implémentation de votre cache est de qualité équivalente à votre application.
Le cache partiel
Le cache partiel est une solution polyvalente de mise en cache. L'outil utilisé pour faire du cache partiel peut également être utilisé pour faire du cache de pages, vu précédemment. Ce système permet de mettre en cache des données, que ce soit des pages entières, des parties de pages, ou des données spécifiques, retournées par des fonctions ou la base de données par exemple.
Une solution de cache partiel est souvent accompagnée des fonctions "put" pour mettre en cache et "get" pour extraire du cache. Une donnée mise en cache est attachée à un identifiant qui est utilisé avec "put" et "get". Par exemple, si vous voulez mettre des produits extraits de la base de données en cache, vous pouvez construire des identifiants avec la clé primaire de chacun de ces produits afin qu'ils soient mis en cache séparément.
On peut implémenter un cache partiel facilement avec les fonctions d'accès aux fichiers ou à la mémoire, ou tout simplement avec des fonctions dédiées au cache telles que celles fournies par la Zend Platform (output_cache_put, output_cache_get, etc.).
Implémentations PHP pour le cache partiel et le cache de pages
Implémenter un système de mise en cache de base est relativement facile. La classe ci-dessous par exemple permet déjà de faire de la mise en cache. Cette classe est optimisée pour une utilisation efficace en PHP 5, nous ne nous étendrons pas sur les aspects de l'architecture. La méthode "put" permet de mettre en cache une donnée en passant une clé et le contenu à stocker. Celui-ci est gardé dans un fichier spécifique dont le nom est dépendant de la clé. La méthode "get" ne fait que retourner le contenu du fichier dont la clé est mentionnée, si ce fichier existe.
- <?php
- // Classe de cache
- abstract class Cache
- {
- {
- if ($cacheDir === null) {
- }
- }
- }
- {
- file_put_contents(self::getCacheFile($key), $value);
- }
- {
- }
- }
- // Utilisation
- $content = Cache::get('yahoo');
- if ($content === false) {
- Cache::put('yahoo', $content);
- }
- echo $content;
- ?>
Bien entendu, nous pouvons améliorer notre classe de mise en cache de plusieurs manières. La première consisterait à implémenter une méthode "clean" qui viderait le contenu du cache, en entier ou en passant une clé. La deuxième consisterait à mettre en place un système de timeout qui permet la mise en cache pour une certaine durée afin de pouvoir renouveler automatiquement les données en cache.
Enfin, un système de mise en cache évolué peut détecter la présence des extensions APC et Zend Platform et utiliser ces outils pour la mise en cache. Pour cela, il suffirait d'implémenter un "proxy" ou un "builder" qui se chargerait de déterminer quelle option utiliser en fonction de la disponibilité des extensions et d'autres caractéristiques (taille des données, fréquence de sollicitation, etc.).
Ce genre de système évolué est efficace car il permet d'exploiter de manière optimale toutes les ressources disponibles de votre environnement. Par exemple, vous pouvez cacher par défaut vos fichiers avec la zend platform, relayer à Apc les données de petite taille très fréquemment utilisées et confier au cache de fichiers les données peu utilisées et de grande taille. L'utilisation de votre classe de cache (Gp_Cache sur la figure) serait très facile du fait que vous n'auriez pas à vous préoccuper de la politique de mise en cache qu'il y a derrière, aussi complexe soit-elle.
Autres solutions d'optimisation
Comme nous l'avons déjà fait remarquer, la mise en cache est une solution d'optimisation, mais qui ne peut en aucun cas pallier aux défauts de performance d'un mauvais algorithme. La première solution d'optimisation à considérer sera donc de veiller à développer avec une architecture, des algorithmes et des solutions techniques fiables. Viennent après quelques manipulations plus ou moins spécifiques dont vous pourrez user en fonction de vos besoins.
Optimiser son savoir-faire et ses connaissances
C'est la meilleure optimisation que nous puissions faire pour nos programmes. Or PHP reste une plateforme très souple qui offre un choix immense tant au niveau de l'organisation du code que de son écriture. Il n'y a pas de "méthode miracle" pour "bien coder" mais des règles à observer :
- L'architecture. Elle revient souvent, celle-ci, mais elle est primordiale. Elle fournit des règles d'organisation du code, désigne la manière dont vous devez séparer et organiser vos données (code, templates, configuration, etc.). Si vous adoptez une architecture objet, vous vous baserez sûrement sur l'implémentation d'un motif de conception (design pattern), le plus souvent MVC (Modèle, Vue, Contrôleur). Il est de l'ordre de l'optimisation que de veiller à ce que l'architecture globale et les conventions qui y sont associées soient respectées.
- Les conventions ! Certaines appartiennent à l'architecture, d'autres à l'écriture du code proprement-dit ou à l'organisation / la dénomination (le nommage) de vos fichiers. Respecter des conventions permet de gagner tout ce que l'on perd à développer de manière anarchique et précipitée : maîtrise du code, facilitation des échanges, organisation favorisant la compréhension du code, etc. Respecter des conventions n'optimise pas le code me direz-vous ? Faites donc l'essai vous même : tentez un projet anarchique et un projet lié à des conventions, puis faites des tests de charge au bout de 3 mois de travail.
- La connaissance des fonctions. Beaucoup de développeurs ne connaissent pas tout ce dont PHP est capable de faire "nativement", c'est à dire par lui-même, grâce à des fonctionnalités développées et compilées en C. Alors on réinvente la roue en PHP, ce qui densifie le code et alourdit inutilement les algorithmes. Passer une petite heure à découvrir les extensions dans la documentation de PHP peut faire gagner énormément de temps à votre application, pensez-y !
- L'informatique fondamentale, le génie logiciel. C'est le point faible des développeurs PHP, qui ont pour la plupart découvert et adopté ce langage parce qu'il était simple et ne nécessitait pas "d'avoir fait Polytechnique" pour décrocher un "hello world". Mais quand on développe, ne pas maîtriser les concepts de collection, d'arbre, d'interface, ou ne jamais avoir travaillé avec des patterns, cela ferme des portes, dresse des barrières. Bien sûr, on peut très bien développer sans cela grâce à PHP, mais qui peut le plus peut le moins, et cela joue aussi sur les performances.
Si vous ne savez pas comment mettre en oeuvre des conventions et une architecture globale, alors rien de mieux que d'opter pour un bon framework. Le Zend Framework est un excellent outil dans ce domaine, car il propose des règles de codage précises, ainsi qu'une architecture cohérente et simple.
Optimiser la bande passante
Parfois, bien que rarement, le goulot d'étranglement se situe au niveau du réseau, dont les tuyaux ne supportent pas la charge. Dans ce cas, il vous est possible d'envoyer aux navigateurs de vos visiteurs des informations compressées. Cette solution permet de réduire de 5 fois en moyenne la taille d'une page HTML. En revanche, quelques navigateurs anciens ne supportent pas les données compressées. De plus, les fichiers binaires, tels que les images, ne sont pas compressés par ce biais.
Il est possible de mettre en oeuvre une telle optimisation avec votre serveur HTTP. Avec Apache par exemple, vous disposez de l'extension mod_gzip qui active la compression des données HTML. Côté PHP, la zend platform permet de mettre en oeuvre de manière autonome cette fonctionnalité.
Optimiser les transferts binaires
Si vos pages supportent de nombreuses images, alors de nombreuses requêtes seront envoyées au serveur par votre navigateur pour les afficher. Pour chaque appel, le serveur utilise un "thread", c'est à dire un processus qui traite votre demande. Entre 4 et 70 thread en moyenne peuvent être générés par un serveur HTTP comme Apache pour desservir en parallèle de nombreuses données.
L'optimisation dont il est question ici consiste en l'utilisation d'un outil qui va se substituer aux thread de votre serveur HTTP, de manière à gérer le transfert des images avec des threads plus légers et performants. Actuellement, un seul outil permet de faire cela : le Zend Download Server, fourni avec la Zend Platform.
Conclusion
Les performances de votre application doivent être considérées en permanence, à chaque étape de développement : votre modélisation doit permettre des performances optimales (limitation des classes, factorisation des algorithmes, modularité facile à cacher...), votre architecture technique aussi (limiter les includes, limiter les accès BD et fichiers, prévoir des algorithmes performants...), et biensûr, votre code (éviter le copier-coller, pratiquer le refactoring, le débogage et le profiling...). Viennent ensuite des solutions "d'optimisation" dont il est question dans cet article : couche supplémentaire à implémenter sur une application stable et fiable (ajout d'un cache d'op-codes, cache partiel ou cache de pages, compression...).
L'auteur
Guillaume Ponçon est architecte / formateur PHP chez Anaska.
Auteur du livre Best practices PHP 5, il contribue au succès de nombreux projets PHP professionnels.
