Blog ENI : Toute la veille numérique !
🎃 Jusqu'à -30% sur les livres en ligne, vidéos et e-formations.
Code : GHOST30.
Cliquez ici !
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
  1. Livres et vidéos
  2. Ecrire du code .NET performant
  3. Profilage d’une application .NET
Extrait - Ecrire du code .NET performant Profilage, benchmarking et bonnes pratiques (2e édition)
Extraits du livre
Ecrire du code .NET performant Profilage, benchmarking et bonnes pratiques (2e édition) Revenir à la page d'achat du livre

Profilage d’une application .NET

Gestion de la mémoire par .NET

1. Principes de base

La plate-forme .NET comprend un gestionnaire de mémoire en charge de l’affectation et de la libération de la mémoire. Ces deux opérations fonctionnent bien sûr de concert, mais dans un mode très différent de C++ ou d’autres langages où la mémoire est gérée manuellement par le développeur.

En .NET, au lieu de laisser au développeur le soin de gérer ces deux opérations, le runtime prend en charge la totalité de la seconde opération, à savoir le nettoyage de la mémoire. Le développeur réserve de la mémoire en créant une instance d’une classe grâce au mot-clé new, mais ne se trouve pas dans l’obligation de la libérer explicitement par la suite (même s’il reste possible de mettre en place des mécanismes avancés de nettoyage de la mémoire). C’est le rôle d’un module de .NET appelé ramasse-miettes (ou Garbage Collector, parfois abrégé en GC, en anglais). Ce dernier se chargera, lorsque le besoin s’en fait sentir, de collecter la mémoire désormais inutile et de la remettre à disposition du programme. Nous allons détailler son fonctionnement dans les chapitres suivants.

La majorité des développeurs .NET ne prennent pas garde à la façon dont la mémoire est gérée. Il s’agit ici d’un bel accomplissement de la plate-forme, car si cela est autant transparent, c’est que le processus est efficace. Néanmoins, connaître le fonctionnement de cette gestion permet, dans les cas les plus avancés, de pouvoir répondre et traiter d’éventuels problèmes, que nous détaillerons plus en détail au fil du chapitre.

2. Gestion de mémoire automatisée et performances

Comme pour toutes les techniques simplifiant le travail de programmation, des polémiques sont nées sur la perte de performance liée à l’utilisation des ramasse-miettes. Pour couper court à une discussion potentiellement stérile, revenons simplement aux principes d’amélioration de la performance d’une application, tels qu’exposés au début...

Particularité des fonctions inline

1. Mécanisme des fonctions inline

Une fonctionnalité de .NET, par ailleurs présente dans d’autres langages, est la notion de fonction inline (il s’agit du terme consacré, qui est utilisé tel quel en français). Ce mécanisme s’utilise concrètement sous forme d’attribut à placer au-dessus d’une méthode. Lors d’un appel à cette méthode, au lieu d’effectuer un saut en mémoire vers l’emplacement du code machine correspondant, .NET recopie la portion de code concernée directement à l’endroit de l’appel de la fonction. Bien évidemment, cette optimisation n’est active que lorsque le code en question est très réduit (en l’occurrence, la documentation indique que la taille maximum du code IL est de 16 octets).

L’exemple ci-dessous montre deux fonctions, Fonction1 et Fonction2, qui réalisent le même calcul de deux manières. La première calcule directement le résultat voulu, tandis que la seconde passe par une fonction intermédiaire.

using System; 
 
namespace FonctionInline 
{ 
    class Program 
    { 
        static void Main(string[] args) 
        { 
            int resultat = Fonction1(1); 
            resultat = Fonction2(1); 
        } 
 
        private static int Fonction1(int Parametre) 
        { 
            int Resultat = Parametre * 2; 
            return Resultat + 1; 
        } 
 
        private static int Fonction2(int Parametre) 
        { 
            int Resultat = FonctionIntermediaire(Parametre); ...

Impact de la gestion mémoire sur la performance

1. Une grande diversité dans les impacts

Tous ces concepts étant posés, quels sont les impacts de la gestion mémoire sur les performances d’une application ? Ceux-ci sont multiples.

Bien évidemment, le principal impact de la mauvaise performance d’une application est celui d’une mémoire trop chargée : plus grande est la quantité de mémoire utilisée, plus le système prend de temps pour la gérer, et surtout, lorsque sont atteintes les limites de la mémoire physique, le système va voir sa performance se dégrader fortement, voire refuser de répondre et remonter une exception signifiant qu’il n’y a plus de mémoire disponible (System.OutOfMemoryException).

Mais des impacts à long terme peuvent aussi être rencontrés. Typiquement, des ressources qui ne sont pas libérées et qui petit à petit vont encombrer la mémoire jusqu’à saturation.

Enfin, nous pouvons constater des lenteurs ou une mémoire trop encombrée simplement par fragmentation. Bref, tout un ensemble d’impacts très diversifiés que nous allons détailler dans les prochaines sections.

2. Utilisation de la mémoire virtuelle

D’un strict point de vue quantitatif, une trop forte occupation mémoire ralentit les performances de l’ensemble du système, car celui-ci est alors obligé d’utiliser la mémoire virtuelle, et dépend donc partiellement de la vitesse du stockage au lieu de tout conserver en mémoire vive. L’impact dans ces cas peut être très important, notamment lorsqu’il est fait usage d’un disque mécanique.

Ceci se passe bien avant d’obtenir une OutOfMemoryException. En effet, dans la plupart des systèmes d’exploitation, un programme obtient un espace mémoire qui lui apparaît comme étant contigu, mais l’OS réalise en arrière-plan une correspondance avec la mémoire vive ou bien avec l’espace connu sous le nom de swap. Ce swap constitue une réserve de mémoire très lente en comparaison avec la mémoire vive, mais qui a néanmoins le mérite d’exister. Cette mémoire est lente...

Les autres ressources à surveiller

1. La mémoire n’est pas tout

Les sections précédentes expliquaient comment la consommation mémoire pouvait affecter la performance d’une application .NET. Bien que les problèmes liés à la RAM constituent une des sources majeures de ralentissement, ils sont loin d’être les seuls. Toutes les ressources d’une machine, ou d’un système, peuvent constituer des goulets d’étranglement.

2. Le CPU

En tout premier vient la consommation processeur. C’est le facteur limitant le plus courant dans la performance d’une application. C’est également le critère le plus difficile à étudier. Sur le graphique ci-dessous, le processeur est-il correctement utilisé ?

images/02EI24.png

Remarquons tout d’abord que les deux cœurs sont utilisés, ce qui est le signe d’une application multithreadée. Bien que ceci soit désormais une pratique standard pour les logiciels destinés à une importante diffusion, c’est encore loin d’être un comportement garanti sur un logiciel réalisé sur mesure pour une entreprise par une ESN, ou même certains éditeurs logiciels. La démocratisation du mode de programmation asynchrone a largement contribué à rendre plus accessible le développement d’applications multi-threadées. Cependant, il existe toujours des applications qui n’exploitent pas les capacités matérielles à leur plein potentiel. Sans compter que la facilité de créer du code asynchrone peut également créer une occupation inutile du processeur, qui aurait pu être utilisé sur d’autres tâches.

Ensuite, que dire du fait que la consommation processeur soit hachée, et à une moyenne inférieure à la moitié de ce qui est disponible ? Est-ce un bon point pour l’application, à savoir qu’elle est capable de se contenter de peu de CPU ? Ou un autre angle est-il raisonnable, à savoir de considérer qu’elle ne tire pas parti de toutes les ressources disponibles ?

La réponse dépend du contexte. Prenons un second exemple :

images/02EI25.png

Cette fois-ci, nous remarquons que le processeur a été utilisé à...