Performances de votre site, au delà du code PHP

Cet article s’agit d’une retranscription de la conférence de Eric Daspet au Forum PHP 2008.

Quelques chiffres

En 2003, les pages HTML faisait en moyenne 100Ko (images comprises) et impliquaient une centaine de composants.

Aujourd’hui, fin 2008, les pages font en moyenne 300 Ko pour 300 composants, soit une augmentation assez conséquente.

D’après les calculs Google, un diminution de 30% du poids de la page (entrainant bien évidement une baisse conséquente de trafic et de temps de chargement) produit chez eux une augmentation de du trafic de 30%, ce qui montre l’importance du temps de chargement de la page pour les utilisateurs. En effet, même inconsciemment, tous les utilisateurs de site web choisissent et préfèrent un site web rapide à charger qu’une bibliothèque d’images…

Où agir ?

Optimiser PHP (en général, vos scripts serveurs) c’est bien, c’est utile. Cependant, il faut se rendre compte que le temps d’exécution de votre script coté serveur influe (en moyenne) de seulement 5% sur le temps de chargement de votre page web ! 95% du temps de chargement est du à de nombreuses requêtes HTTP avec un temps de latence important et pour un peu une parallélisation nulle ou de peu d’éléments.

Le back-end est un système important mais l’optimisation du front-end offre un bien meilleur ROI (Return Of Investissement, Retour sur investissement en français) qu’une revue sur back-end.

Quels objectifs ?

De nombreuses requêtes HTTP est la principale cause de ralentissement de la page. En effet, que peu d’éléments sont télécharger en parallèle (on appelle ça la parallélisation) et chaque requête envoyée au serveur attend, bien évidement, la réponse… Cette réponse peut mettre plusieurs dizaines (voire centaines sur les serveurs ne se trouvent pas sur le même continent par exemple, ou que le trafic est très engorgé) de milisecondes de latence. Le démarrage d’une requête devant attendre la fin de la précédente, le chargement de la page est considérablement ralenti.

De plus, après avoir réduit le nombre de requêtes HTTP, on va essayer de réduire la taille de toutes les données envoyées par le serveur et reçues par le navigateur du client.

Le cache HTTP

Le navigateur utilise un outil fameux, qu’est le cache. En plus des différents cache de votre application, au niveau de client, le navigateur essaye de charger le moins de contenu statique et de les stocker en local pour une utilisation ultérieure.

Pour dire au navigateur qu’il faut qu’il garde le fichier (que ce soit une image, un fichier PHP, un CSS, du JS, etc…) en cache, nous allons utiliser seulement deux entêtes HTTP :

Expires: Wed, 24 Oct 2018 21:32:33 GMT

Qui met en cache le fichier jusqu’en 2018, ou…

Cache-Control: public, max-age: 3600

…qui met le fichier en cache pendant une heure.

Ces entêtes, vous pouvez les définir via la fonction « header » de PHP ou simplement grace à un fichier HTACCESS placé dans un répertoire parent du fichier concerné.

Note: Vous pouvez définir uniquement Cache-control, seulement, le navigateur va quand même interroger le serveur HTTP pour connaître la date de modification et définir si, oui ou non, le fichier a été modifié. S’il l’a été, le navigateur récupère le nouveau fichier, sinon, il le prend en cache. Cette méthode permet d’économiser de la bande passante et accélère un peu l’acquisition du fichier par le client mais le temps de latence entre le client et le serveur est toujours là.
L’utilisation de l’en-tête Expires permet tout simplement de dire au navigateur de prendre directement le fichier dans son cache, et de ne pas demander quoi que se soit au serveur à propos de ce fichier.

Le problème, c’est que par exemple, vous avez un fichier Image « header.png » qui est l’image affichée à l’entête de votre site. Nous sommes en période de fête donc vous souhaitez afficher une image simpatique avec de la neige. Vous avez précédemment demander aux navigateurs de mettre en cache l’image jusqu’en 2018 (exemple précédent) : les visiteurs n’auront la nouvelle image que si vous changez de nom. Ce problème ce pose en fait surtout pour le Javascript qui est amené à changer un peu plus que les images.

Pour contrer ce (petit) problème, ajoutez un argument URL à l’adresse de l’image. Pour le navigateur, le fichier ne sera pas le même, pour Apache (ou une autre application), c’est toujours le fichier « header.png ». Par exemple :

<script type=’text/javascript’ src=’monjs.js?<?php echo filemtime(‘./monjs.js’); ?>’></script>

La date de modification sera ajoutée automatiquement à chaque chargement de page. Un cache sur le résultat de cette fonction est automatiquement mis dans PHP mais il ne reste pas très longtemps.

Pré-charger des images ?

Utilisé par Google, le principe est simple : sur votre page d’accueil, vous chargez des images qui ne vont pas vous servir pour cette page mais, plus tard. Tout en bas de la page (important car comme ça, les utilisateurs croient que la page est complètement chargée alors qu’il y a toujours des téléchargements), utilisez du Javascript pour faire charger l’image par le navigateur et qu’elle soit mise en cache:

var myImage = new Image();
myImage.src = ‘http://adresse/de/l/image.ext';

Et l’image est chargée..

Concaténation et compression

Pour accélérer encore une fois le temps de chargement, nous allons maintenant travailler sur la taille du fichier. Supprimer de nombreux espaces dans les fichiers JS, CSS et HTML peut faire gagner près de 30% de taille de fichier ! Pour les fichiers javascript, l’utilisation de Compressor Rater ou YUICompressor permet d’économiser parfois jusqu’à 70% de la taille ! Un fichier de 10Ko passe alors à 3 Ko. D’après une réctification d’Eric Daspet, “c’est encore mieux que ça, un fichier de 100k peut facilement passer en dessous de 10ko voire 7ko.”

En général, sur de nombreux sites, les fichiers Javascript sont séparés en plusieurs et de même pour les fichiers CSS. Cette pratique est un gain important de lisibilité mais qui ralenti considérablement le temps de chargement du site. En effet, admettons que pour un site quelconque, on ai 5 fichiers de 20 Ko de code (concatené) et que entre le client et le serveur, il y ai une latence de 100ms et un transfert de 100Ko/s (<=> 0.1 Ko/ms )
Avec les 5 fichiers distincts, on obtient: 5*100 + 20*5*0.1 ms. Soit 510ms uniquement pour les scripts Javascript !
Si les fichiers étaient tous en un, on obtient un fichier de 100Ko. On a donc: 100 + 100*0.1. Soit 110ms. Une division de près de 5 pour les mêmes données !

Le même principe est tout à fait utilisable sur les images. Si vous utilisez des petites icônes, regroupez-les dans une seule image et utilisez la propriété CSS background-postition pour choisir quelle partie de cette grande image (appelée sprites) afficher.

Une fois ces réductions effectuées, on peut maintenant essayer de diminuer encore plus le poids des données échangées entre le navigateur et le serveur. Pour cela, pour allons compresser les données sortantes du serveur directement avec un module Apache: mod_deflate (Apache 2) ou mod_gzip (Apache 1).

En ce qui concerne les images, voici quelques conseils:

  • Malgré des croyances bizarres, les images PNG sont souvent moins lourdes pour la même qualité. N’utilisez pas GIF si PNG est mieux.

  • Passez toutes les images par PNGCrush avant de mettre en ligne : Photoshop fait un travail assez (voire très) bâclé là-dessous. PNGCrush essaye un grand nombre de compressions pour trouver la meilleure, sans pertes d’erreurs bien évidement. Un autre outils sur web existe aussi, c’est www.Smushit.com.

  • Retirez les méta-données des images.

  • Utilisez PNG8 (256 couleurs) à la place de PNG24 car très souvent, on ne voit pas la différence mais la taille est inférieur en PNG8.

  • Ne pas utiliser Alpha Image Loader. Cette fonctionnalité fait perdre en moyenne 100ms (!!) de traitement par image sur le navigateur.

Parallélisation

Maintenant, beaucoup de navigateurs supportent les téléchargements en parallèle. Cette méthode permet de ne pas trop être atteint par les latences lors du chargement d’un fichier. Les navigateurs téléchargent entre 2 et 8 fichiers simultanés (IE6 peut faire 2 téléchargements simultanés maximum).

Une balise Javascript bloque toute autre opération. A partir du moment où le moteur de rendu est arrivé sur une balise Javascript qui nécessite un téléchargement du fichier depuis le serveur, toutes les autres opérations sont arrêtées: la lecture du code est finie. Ce phénomène est embêtant mais logique. En effet, le fichier JS peut modifier toute la structure du HTML et par conséquent modifier la liste des fichiers à télécharger.

Pour contrer ce problème, chargez-les dynamiquement :

var js = doucment.createElement(‘script’);
js.src = ‘myscript.js';

Javascript

Une modification de la taille, de la couleur ou quelque autre paramètre graphique d’un élément HTML affiché sur la page web implique une nouvelle exécution du moteur de rendu.

Prenons un exemple pour expliquer ce problème: Au clic sur un bouton, vous voulez modifier la couleur de tous les liens (les mettre en vert). Vous allez faire une boucle sur tous les objets a et vous allez faire un obj.style.color = ‘#00FF00′;
C’est très mauvais au niveau de la performance pour le navigateur. En effet, si vous avez 50 liens (ce qui n’est pas excessif) vous allez impliquer 50 nouveaux rendus et 50 lectures.

Note: Une lecture implique un rendu avant elle. C’est-à-dire qu’avant une lecture, après une écriture (mise à jour d’un élément par exemple), il y a un rendu.

Pour ne pas faire de rendu à chaque fois, mettez les éléments à mettre à jour dans un liste. Une fois la liste composée, parcourez cette nouvelle liste et mettez à jour les éléments. Le navigateur va détecter qu’ils n’y a que des écritures et va lancer un rendu uniquement à la fin de toutes les modifications.

Quelques liens

facebooktwittergoogle_plusredditpinterestlinkedinmail

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">