Performance et optimisations

Ce document présente une variété de techniques et d’outils pour aider à rendre votre code Django plus performant, plus rapide et moins gourmand en ressources système.

Introduction

En général, la première préoccupation est d’écrire du code qui fonctionne, avec une logique qui fonctionne comme prévu pour produire le résultat attendu. Cependant, il arrive parfois que ce ne soit pas suffisant pour que le code fonctionne de manière efficace comme on pourrait le souhaiter.

Dans ce cas, il s’agit de faire quelque chose, et en pratique, souvent plusieurs choses pour améliorer la performance du code sans affecter son comportement, ou seulement de manière minimale.

Approches générales

Dans quel but optimiser ?

Il est important d’avoir une idée claire de ce que signifie « performance » pour vous. Il n’y a pas qu’une seule façon de la mesurer.

L’amélioration de la vitesse d’exécution peut paraître l’objectif le plus évident pour un programme, mais il peut parfois être intéressant de rechercher d’autres gains de performance, comme une consommation mémoire réduite ou une moins grande sollicitation de la base de données ou du réseau.

Des améliorations dans un domaine génèrent souvent une amélioration de performance dans d’autres domaines, mais pas toujours ; il peut arriver que l’inverse se produise. Par exemple, une augmentation de la rapidité d’un programme peut être la cause d’une plus grand consommation de mémoire. Pire encore, l’effet peut se retourner contre lui-même, par exemple dans le cas où l’amélioration de rapidité est tellement exigeante en mémoire que le système commence à en manquer, le résultat final risque d’être pire qu’avant.

Il y a d’autres compromis à prendre en compte. Votre propre temps est une ressource précieuse, plus que le temps CPU. Certaines améliorations pourraient être trop difficiles pour valoir vraiment la peine, ou pourraient affecter la portabilité ou la maintenance du code. Les améliorations de performance ne valent pas toutes la peine d’être mises en œuvre.

Vous devez donc être bien conscient des améliorations de performance que vous visez et vous devez aussi connaître la bonne raison qui vous pousse dans cette direction. Pour cela, vous avez besoin de :

Analyse des performances (« benchmarking »)

Il ne sert à rien de simplement deviner ou de faire des suppositions sur les points faibles de votre code.

Outils Django

django-debug-toolbar is a very handy tool that provides insights into what your code is doing and how much time it spends doing it. In particular it can show you all the SQL queries your page is generating, and how long each one has taken.

Il existe aussi des panneaux complémentaires de tierce partie pour cet outil, parmi lesquels un outil de rapport sur la performance du cache et un autre sur le temps de production des gabarits.

Services tiers

Il existe un certain nombre de services gratuits qui analysent et détaillent la performance des pages d’un site dans la perspective d’un client HTTP externe, simulant ce que pourrait expérimenter un utilisateur réel.

Ces outils ne peuvent pas donner de détails sur les éléments de code internes, mais peuvent donner une bonne idée de la performance générale d’un site, y compris sur des aspects qui ne peuvent pas être mesurés proprement depuis l’environnement Django. Parmi ceux-ci, on trouve :

Il existe aussi un certain nombre de services payants qui effectuent de telles analyses, parfois même orientés Django et pouvant prendre en compte le code du site pour étudier sa performance de manière bien plus complète.

Partir du bon pied dès le début

Une partie du travail d’optimisation concerne la correction de lacunes en terme de performance, mais un autre aspect fait simplement partie du travail de développement régulier, dans le cadre de bonnes pratiques à suivre avant même de réfléchir spécifiquement à l’amélioration de performance.

Dans cette optique, Python est un excellent langage de développement, car les solutions qui paraissent élégantes et qui ont l’air correctes sont généralement aussi celles qui sont les plus efficaces. Comme pour la plupart des compétences, l’apprentissage de ce qui « a l’air correct » demande de la pratique, mais l’une des consignes les plus utiles est :

Travailler au niveau approprié

Django offre de nombreuses manières différentes d’approcher les choses, mais ce n’est pas parce qu’il est possible de faire quelque chose d’une certaine manière que c’est forcément la manière la plus correcte de le faire. Par exemple, vous pouvez constater qu’une même quantité, le nombre d’éléments d’une collection, peut être calculée au niveau d’une requête QuerySet, en Python ou dans un gabarit.

Cependant, il est en principe toujours plus rapide de faire ce genre de calcul au niveau le plus bas possible. À des niveaux supérieurs, le système interagit avec les objets à travers plusieurs niveaux d’abstraction et de couches de machinerie.

C’est-à-dire que la base de données peut généralement faire les choses plus rapidement que Python, qui lui-même les fait plus rapidement que le langage de gabarit :

# QuerySet operation on the database
# fast, because that's what databases are good at
my_bicycles.count()

# counting Python objects
# slower, because it requires a database query anyway, and processing
# of the Python objects
len(my_bicycles)

# Django template filter
# slower still, because it will have to count them in Python anyway,
# and because of template language overheads
{{ my_bicycles|length }}

En général, le niveau le plus approprié pour effectuer un travail est celui de plus bas niveau avec lequel il est encore aisé d’écrire le code.

Note

L’exemple ci-dessus est purement illustratif.

Tout d’abord, dans un contexte réel, il faut considérer ce qui se passe avant et après le dénombrement pour trouver quelle est la manière optimale de le faire dans ce contexte particulier. Les documents d’optimisation de la base de données décrivent un cas où le dénombrement au niveau du gabarit est la meilleure solution.

Deuxièmement, il faut considérer d’autres options : dans un cas réel, {{ mes_velos.count }} qui invoque directement la méthode de QuerySet count() à partir du gabarit pourrait constituer le choix le plus judicieux.

Mise en cache

Il est souvent coûteux (consommateur de ressources et lent) de calculer une valeur, il est donc potentiellement très bénéfique d’enregistrer la valeur dans un cache à accès rapide, prête pour une réutilisation ultérieure.

Il s’agit d’une technique suffisamment importante et puissante pour que Django daigne inclure un système de cache complet, de même que d’autres éléments plus petits liés à la fonctionnalité du cache.

L’infrastructure de cache

L’infrastructure de cache de Django présente des possibilités réellement avantageuses en terme de gains de performance, en enregistrant du contenu dynamique afin qu’il ne doive plus être calculé pour chaque requête.

Par commodité, Django offre différents niveaux de granularité de cache : vous pouvez mettre en cache le résultat de vues spécifiques ou uniquement les éléments qui sont difficiles à produire, ou même un site complet.

La mise en œuvre du cache ne doit pas être considérée comme une alternative à l’amélioration de code peu performant en raison d’une implémentation déficiente. C’est plutôt l’une des étapes finales vers la mise en production d’un code performant, pas un raccourci.

cached_property

Il est courant d’avoir à appeler une méthode d’instance de classe plus d’une fois. Si cette méthode est coûteuse, ces appels récurrents peuvent gaspiller des ressources.

En utilisant le décorateur cached_property, la valeur renvoyée par la propriété est enregistrée ; lors de l’appel suivant de cette fonction sur cette instance, c’est la valeur enregistrée qui sera renvoyée au lieu de la recalculer. Notez que cela n’est valable que pour les méthodes qui acceptent uniquement le paramètre self et que cela transforme la méthode en propriété.

Certains composants de Django possèdent aussi leur propre fonctionnalité de cache ; ils sont abordés ci-dessous dans les sections en lien avec ces composants.

Compréhension du retardement

Le retardement (« lazyness ») est une stratégie complémentaire au cache. La mise en cache évite le calcul récurrent en enregistrant les résultats ; le retardement diffère le calcul jusqu’au moment où il devient réellement utile.

Le retardement permet de se référer à des éléments avant qu’ils ne soient instanciés ou même avant qu’il ne soit possible de les instancier. Les usages sont multiples.

Par exemple, les traductions différées peuvent être utilisées avant même que la langue de destination ne soit connue, car la traduction n’intervient qu’au moment où la chaîne traduite est effectivement nécessaire, comme par exemple lorsqu’elle est affichée dans un gabarit.

Le retardement est aussi une façon d’économiser des efforts en essayant d’éviter carrément une opération. C’est-à-dire qu’un aspect du retardement est de ne rien faire avant que cela ne devienne indispensable et il se peut très bien que cela ne se produise jamais après tout. C’est cet aspect du retardement qui peut affecter la performance ; plus le travail à faire est conséquent, plus il y a à gagner en recourant au retardement.

Python offre un certain nombre d’outils pour l’évaluation différée, particulièrement au moyen des stratagèmes de générateur et d”expression génératrice. Il vaut la peine de s’intéresser au retardement en Python pour découvrir les opportunités d’utilisation des modèles de retardement dans le code.

Retardement dans Django

Django lui-même utilise largement le retardement. Un bon exemple peut être trouvé dans l’évaluation des jeux de requête (QuerySets). Les objets QuerySets sont différés par nature. Par conséquent, un QuerySet peut être créé, transmis et combiné avec d’autres objets QuerySet sans jamais procéder à l’accès réel à la base de donnes pour récupérer les éléments qu’il représente. C’est bien l’objet QuerySet lui-même qui est manipulé, pas l’ensemble des éléments qu’il récupérera au final depuis la base de données.

D’un autre côté, certaines opérations forcent l’évaluation effective des jeux de requête. En évitant d’évaluer prématurément un QuerySet, on peut parfois faire l’économie d’un accès coûteux et inutile à la base de données.

Django propose également un décorateur keep_lazy(). Celui-ci permet à une fonction appelée avec un paramètre différé de se comporter elle-même de manière différée, en retardant son évaluation au moment où elle sera réellement nécessaire. De cette façon, le paramètre différé, qui peut être coûteux en ressources, ne sera pas évalué tant que la situation ne l’exige pas absolument.

Bases de données

Optimisation de la base de données

La couche de base de données de Django offre plusieurs façons d’aider les développeurs à tirer le meilleur de leurs bases de données. La documentation d’optimisation de la base de données rassemble des liens vers la documentation adéquate et ajoute différentes astuces qui détaillent les étapes à suivre lorsque l’on veut optimiser l’utilisation de la base de données.

Performances HTTP

Intergiciels (« Middleware »)

Django contient quelques intergiciels pouvant se révéler utiles pour optimiser les performances d’un site. Il s’agit de :

ConditionalGetMiddleware

Adds support for modern browsers to conditionally GET responses based on the ETag and Last-Modified headers. It also calculates and sets an ETag if needed.

GZipMiddleware

Compresse les réponses pour tous les navigateurs modernes, économisant de la bande passante et du temps de transfert. Notez que `GZipMiddleware est actuellement considéré comme un risque de sécurité car il est vulnérable à des attaques qui annulent la protection fournie par TLS/SSL. Lisez l’avertissement dans GZipMiddleware pour plus de détails.

Sessions

Utilisation des sessions en cache

L”utilisation de sessions en cache peut être un moyen d’améliorer les performances en éliminant le besoin de charger les données de sessions à partir d’un système de stockage plus lent comme la base de données, et de le remplacer par un stockage en mémoire des sessions les plus souvent utilisées.

Fichiers statiques

Les fichiers statiques, qui par définition ne sont pas dynamiques, constituent une cible parfaite pour l’optimisation.

CachedStaticFilesStorage

En tirant avantage des capacités de cache des navigateurs Web, vous pouvez éliminer complètement certains accès réseau pour un fichier donné après son téléchargement initial.

CachedStaticFilesStorage ajoute une étiquette dépendante du contenu aux noms de fichiers des fichiers statiques pour permettre aux navigateurs de mettre en cache à long terme et sans risque de manquer de futures modifications ; dès qu’un fichier est modifié, son étiquette l’est également, ce qui fait que les navigateurs vont automatiquement le recharger.

« Minification »

Plusieurs outils et paquets Django de tierce partie offrent la possibilité de « minifier » des fichiers HTML, CSS et JavaScript. Ils suppriment les espaces blancs, les sauts de lignes et les commentaires inutiles et raccourcissent les noms de variables, ce qui réduit la taille des documents publiés par un site.

Performance des gabarits

Notez que :

  • l’utilisation de {% block %} est plus rapide que l’utilisation de {% include %}
  • les gabarits fortement fragmentés, assemblés par de nombreux petits morceaux, peuvent affecter la performance

Le chargeur de gabarits en cache

L’activation du chargeur de gabarits en cache améliore souvent les performances de manière drastique, car il évite de devoir compiler chaque gabarit lors de chaque rendu.

Utilisation de différentes versions disponibles des logiciels

Il peut parfois valoir la peine de vérifier si des versions différentes et plus performantes des logiciels utilisés sont disponibles.

Ces techniques s’adressent à des utilisateurs plus avancés qui souhaitent repousser les limites de performance d’un site Django déjà bien optimisé.

Cependant, il ne s’agit pas de solutions magiques à des problèmes de performance, et il est peu probable que cela apporte des gains très significatifs à des sites qui ne se conforment pas encore aux bonnes pratiques pour les choses les plus élémentaires.

Note

Il vaut la peine de le redire : la recherche d’implémentations logicielles alternatives n’est jamais la première réponse à apporter à des problèmes de performance. Lorsque vous atteignez ce niveau d’optimisation, il est nécessaire de recourir à des solutions formelles d’analyse de performance.

Ce qui est plus récent est souvent meilleur, mais pas toujours

Il est relativement rare qu’une nouvelle version d’un logiciel bien maintenu soit moins efficace, mais les mainteneurs ne peuvent pas non plus anticiper tous les cas d’utilisation possibles. Ainsi, tout en pouvant supposer que de nouvelles versions sont plus performantes, il n’est pas possible d’être certain que ce sera toujours le cas.

C’est aussi vrai pour Django lui-même. Les versions successives ont apporté un certain nombre d’améliorations dans tout le système, mais il est toujours utile de contrôler les performances en utilisation réelle de votre application, parce que dans certains cas, vous pourriez découvrir que certaines modifications aboutissent à une perte de performance au lieu de l’inverse attendu.

Des versions plus récentes de Python ainsi que des paquets Python sont souvent aussi plus performants, mais mesurer vaut toujours mieux que supposer.

Note

Sauf dans le cas où vous découvrez un problème de performance inhabituel dans une version particulière, les nouvelles versions apportent généralement de meilleures fonctionnalités, une meilleure stabilité et une sécurité améliorée ; en règle générale, ces bénéfices sont plus importants que d’éventuels gains ou pertes de performance.

Alternatives au langage de gabarit de Django

Pour presque tous les cas, le langage de gabarit intégré à Django est totalement adéquat. Cependant, si les points d’étranglement d’un projet Django semblent se concentrer autour du système des gabarits et que vous avez épuisé les autres pistes pour y remédier, une alternative externe peut être une réponse.

Jinja2 peut offrir des améliorations de performance, en particulier au niveau de sa rapidité.

Les systèmes de gabarit alternatifs varient dans leur similitude avec le langage de gabarit de Django.

Note

Si vous rencontrez des problèmes de performance dans les gabarits, la première chose à faire est de comprendre exactement ce qui se passe. L’utilisation d’un système de gabarit externe peut se révéler plus rapide, mais il est peut-être aussi possible d’obtenir la même amélioration sans recourir à cette solution extrême, par exemple en effectuant de manière plus efficace dans une vue la logique coûteuse se trouvant actuellement dans les gabarits.

Implémentations logicielles alternatives

Il peut valoir la peine de vérifier si le logiciel Python que vous utilisez est disponible dans une autre implémentation pouvant exécuter le même code plus rapidement.

Cependant, la plupart des problèmes de performance dans les sites Django bien écrits ne se situent pas au niveau de l’exécution Python, mais plutôt dans des requêtes de base de données trop lentes, une mauvaise exploitation du cache ou des problèmes de gabarit. Si vous vous appuyez sur du code Python mal écrit, les problèmes de performance ne seront probablement pas résolus en exécutant ce code plus rapidement.

L’utilisation d’une implémentation différente peut introduire des problèmes de compatibilité, de déploiement, de portabilité et de maintenance. Il va sans dire qu’avant d’adopter une implémentation non standard, vous devez vous assurer que les gains de performance pour l’application sont suffisamment importants pour justifier les risques potentiels.

En tenant compte de ces mises en garde, il est intéressant de connaître :

PyPy

PyPy est une implémentation de Python en Python (l’implémentation « standard » de Python est écrite en C). PyPy peut permettre des gains de performance substantiels, typiquement pour les applications lourdes.

Un objectif clé du projet PyPy est la compatibilité avec les API et les bibliothèques Python existantes. Django est compatible, mais vous devrez contrôler la compatibilité avec les autres bibliothèques que vous utilisez.

Implémentations en C des bibliothèques Python

Certaines bibliothèques Python sont aussi implémentées en C et sont parfois plus rapides. Elles visent à offrir la même API. Notez qu’il n’est pas rare de rencontrer des problèmes de compatibilité et des différences de comportement (qui ne sont pas toujours immédiatement évidents).

Back to Top