Améliorer la vitesse de chargement pour une application Web ASP.NET MVC 4.0 (Cache, Bundling, Minification)

On ne peut s’empêcher d’être frappé, lors du développement d’une application MVC, par la relative lenteur de l’affichage initial de la page d’accueil. Evidemment, si vous utilisez IIS Express, ou le serveur de développement de Visual studio, ils commencent par charger l’application avant de l’exécuter, ce qui ajoute à cette perception de lenteur. Cependant, même hébergée sur un serveur rapide, cette même impression persiste. On va d’ailleurs voir que ce n’est pas qu’une impression, comme les chiffres vont nous le montrer. Cet article va passer en revue les différents moyens existants pour améliorer la vitesse de chargement initial, et vérifier à chaque fois les progrès mesurés sur une application réelle.

Les outils de mesure

.Tout d’abord, une rapide présentation des outils utilisés pour comparer et mesurer ces temps de chargements. J’en utilise 2, trouvant qu’ils se complètent bien pour mes besoins:

  1. L’onglet “Réseau” des outils de développement d’Internet Explorer
  2. Le speed Tracer de Google Chrome

IEF12Generic

Dans Internet Explorer (Je suis sur IE 10, mais cela devrait fonctionner sur les versions précédentes, 8 et 9), il suffit d’appuyer sur F12 pour faire apparaitre cette fenêtre. Sélectionnez l’onglet Réseau. Dans la fenêtre du navigateur, indiquez l’URL que vous souhaitez tester. Une fois chargée, cliquez sur “Démarrer la capture”, puis, dans le navigateur, appuyez sur F5 pour rafraichir la page. Si vous voulez  obtenir des comparaisons aussi valides que possible, videz le cache du navigateur systématiquement avant l’appui sur F5.

La partie grisée des barres de progression montrent le temps d’attente du navigateur avant que l’action ne commence. La partie jaune indique le temps de requête pour le premier octet, c’est à dire le temps pris pour envoyer le requête et recevoir le premier octet en retour. Les barres bleues indiquent le temps nécessaire au chargement des données elles même. Comme vous pouvez le voir sur cet exemple, ce ne sont pas les données qui prennent du temps pour un chargement depuis le cache!

Un double clic sur une ligne nous amène sur une vue détaillée du temps passé pour l’opération indiquée. Dans l’exemple ci-dessus, un double clic sur la ligne se terminant en dernier va nous donner le temps total entre la première requête au serveur et les dernières données chargées:

IEF12totaltempsgeneric

On voit ici qu’il a fallu attendre 1 seconde et 41/100ème avant que la requête ne soit lancée, et qu’au total il a fallu 2 secondes 32/100 pour qu’elle se termine.

Ces indication données par Internet Explorer sont précieuses, mais elles ne disent pas toute la vérité.

Cette extension à Google Chrome, Speed Tracer, par différence, enregistre TOUS les évènements de la page, et va nous permettre d’affiner les indications données par IE et de les compléter. Nous reviendrons en détail sur  Speed Tracer lors des mesures effectuées sur nos améliorations incrémentales.

Etat des Lieux

Avant de chercher à améliorer quoi que ce soit, voyons où nous en sommes. L’application, hébergée sur un site web azure (ça élimine les risques de lenteur du serveur), est “brute de fonderie”, c’est à dire que RIEN de particulier n’a été fait pour l’optimiser, si ce n’est que les scripts JavaScripts sont chargés en bas de page. c’est tout.

IESansCache

En ayant pris soin de vider le cache du navigateur avant la mesure, Internet Explorer nous indique un temps total de 4,47 secondes.

Cache coté serveur

La première chose à faire, et de loin la plus facile, est de mettre en cache coté serveur les parties statiques de la page. Dans mon cas, la page est très dynamique, avec plusieurs fonctions appelées par Ajax. Si vous mettez tout en cache, il n’y aura plus rien de dynamique, agissez donc avec discernement.

L’attribut OutputCache est tout ce dont j’ai besoin pour que le serveur mette en cache la vue “Index”. Le paramètre Duration donne le temps de validité du cache. Comme il s’agit de secondes, 86400 correspond à 24 heures. Le cache coté serveur ne change rien au temps de chargement initial, pour le premier internaute qui va sur la page, par contre, les améliorations pour les suivants sont perceptibles. La même mesure que ci-dessus (dernier élément chargé), dans les mêmes conditions (cache coté navigateur vidé) nous montre un gain d’un peu plus d’une seconde:

IECacheServeur

Pas trop mal pour une modification aussi simple et rapide à faire. Voyons maintenant plus en détail pourquoi nous avons des délais avant que certaines opérations ne commencent, et comment y remédier.

Bundling et Minification

MVC 4.0 permet d’office l’utilisation de “Bundles” (Cette opération est possible avec MVC3 avec un package NuGet). A quoi ça sert? Comme on le voit sur la première image  des temps de chargement dans Internet Explorer, de nombreuse opération doivent attendre avant de pouvoir démarrer parce que le navigateur limite le nombre de connexions simultanées à 6. Si votre page requière le chargement de plus de 6 éléments, les autres sont mis en file d’attente, et ainsi de suite. Entre Javascript, les fichiers CSS, quelques images et icones, il y a de très fortes chances qu’il y ait plus de 6 éléments dans votre page. Le “Bundling” va nous permettre de combiner plusieurs fichiers, de manière à ce qu’ils soient “vus” par le navigateur comme un seul élément, ce qui devrait nous permettre d’économiser (au moins en partie) sur ce temps d’attente.

Avant de faire quoi que ce soit, voyons où nous en sommes:

IEBeforeBundling

Sans entrer dans les menus détails, on voit tout se suite qu’il y a une erreur 404 sur une ressource, pas mal d’attente avant les requêtes pour de nombreux fichiers, et beaucoup de bleu, donc de chargement de données (qui peuvent être aussi bien des fichiers Javascript que des images). Commençons donc à combiner nos fichiers en bundles avant de mesurer combien de temps nous avons gagnés.

Nous pouvons  combiner dans un bundle 2 types de ressources: les fichiers de style, CSS, et les fichiers de scripts, JS. Mais pour être encore plus efficaces,  avant de combiner nos fichiers en bundle, nous allons les minifier. Encore un nouveau mot bizarre! Pensez à amplification, où maximisation, mais à l’envers! Minimiser à une connotation péjorative, et nous n’allons certainement pas minimiser l’importance de nos fichiers de style ou de scripts, mais bien les minifier, c’est à dire les rendre les plus petits possibles en terme d’octets à charger. Comment fais-t-on cela? Pour une fois, et sauf cas particuliers, il n’y a rien à faire! En effet, le simple fait de créer un Bundle suffit pour que les fichiers soient minifiés avant d’être combinés. Comme des fichiers JavaScripts (et css) sont impossibles à lire (sauf crise de masochisme aigüe), le bundling n’intervient réellement qu’en configuration “Release”, rien  ne se passe en mode “Debug”.

La première chose que nous allons faire est donc de passer le mode debug à false dans web config:

Ensuite, dans le dossier App_Start, le fichier BundleConfig.cs contient déjà (si vous ne l’avez pas encore modifié) des définitions pour un certain nombre de Bundles.

J’ai remplacé les définitions pré-existantes pour n’avoir que les fichiers que j’utilise effectivement dans cette application. Vérifiez que TOUS les fichiers CSS utilisés par l’application sont bien répertoriés dans ce Bundle et qu’aucun d’entre eux n’est inclus directement dans une balise link d’un fichier cshtml. Comme je trouve plus pratique de faire directement au fur et à mesure pendant que je suis en phase de développement, tous mes fichiers css sont inclus directement dans des balises link en html, avant d’être remplacés par le Bundle.

La même chose est faite pour les fichiers Javascript.

IEAfterBundlingCCSetJS

Au niveau du temps de chargement initial, le gain n’est pas immense, on est passé de 3,26 secondes à 3,16 secondes, mais on voit clairement que notre première image est chargée sans délai, alors que précédemment il y avait 5 fichiers chargés avant elle. Le temps de chargement du Bundle CSS est de 0.73 secondes, alors qu’initialement un des fichiers css prenait à lui tout seul 1.21 secondes. Le nombre de fichiers est passé de 26 à 16, et l’on voit du premier coup d’œil qu’il n’y a plus aucun délai à vide (zone grisée). Tout n’est pourtant pas encore parfait; notamment avec JSTree qui ne trouve plus son style s’il est mis en Bundle, il y a encore un peu à gagner de ce coté.

Qu’en est-il de cette fameuse minification? Pour vérifier qu’elle a bien lieu, un double clic sur la ligne /bundles/css va nous permettre de voir à quoi nos fichiers ressemblent, vus par le navigateur:

MinifiedCSS

Ca ressemble bien a du CSS, mais s’imposer de lire cela est vraiment une punition imméritée…Tous les espaces et commentaires sont supprimés. En Javascript, en plus, les noms de variables sont aussi modifiés pour être raccourcis au minimum de lettres.

Nous pourrions encore améliorer nos temps de réponse en utilisant un CDN (Content Delivery Network) pour nos Bundles, mais nous reviendrons sur cette technique dans un prochain billet.

Cache coté client

Il nous reste une dernière chose simple à faire, nous assurer que tout notre contenu statique est bien mis en cache coté client. Toutes nos mesures ont été faites après avoir vidé le cache du navigateur, de manière à ce que les comparaisons soient valides. Dans la pratique, il est pourtant bien rare que l’utilisateur vide systématiquement son cache. En admettant que l’internaute ait envie de retourner sur votre site (!), autant utiliser au mieux les capacités du navigateur pour rendre son expérience plus agréable. Pour cela, il nous faut spécifier un délai d’expiration pour les diverses ressources fixes. Un simple Web.config dans les répertoires concernés (Content et Scripts) va permettre d’indiquer cette propriété:

La date d’expiration est fixée a dans 3 ans. De cette manière, le navigateur ira chercher la ressource dans le cache, sans passer par une requête au serveur.

Résultats et Conclusion

Si vous vous regardez les toutes premières copies d’écran de cet article, elles concernaient les temps de chargement de la même application ASP.NET MVC 4.0, depuis le cache du navigateur, avant qu’aucune tentative d’optimisation ne soit faite. Le temps total indiqué est de 2 secondes 32.

FinalResultfromcache

Dans les mêmes conditions, nous en sommes maintenant à 1 secondes 01. Notre travail a payé, de manière significative. Pourtant, il reste des améliorations possible. Celle, déjà indiquée, concernant JSTree, et l’utilisation d’un CDN. Ce sera l’occasion d’un nouveau billet. En attendant, vos remarques et suggestions sont comme toujours les bienvenues!

Références pour approfondir le sujet:

http://blogs.msdn.com/b/rickandy/archive/2011/05/21/using-cdns-to-improve-web-site-performance.aspx

http://www.asp.net/mvc/tutorials/mvc-4/bundling-and-minification

Publié dans ASP.NET MVC 4.0, Web Tagués avec : , , ,

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

*

Lettre d’information

Recherche sur le Site

Recherche personnalisée