cogimator.net

Une ligne à la fois...

Mise en cache intelligente des templates AngularJS

Avant de rentrer dans le vif du sujet, petit rappel sur le fonctionnement du cache du navigateur.

Lorsque le navigateur demande une ressource (index.html par exemple) au  serveur, celui ci lui renvoie plusieurs éléments :

  • un statut HTTP (200 SUCCESS ici)
  • le contenu du fichier index.html
  • un ensemble d’en-têtes http

Parmi ces en-têtes, on retrouvera (en fonction de la configuration du serveur) :

  • un ETAG, qui est un checksum calculé selon le contenu renvoyé au navigateur
  • une date d’expiration, qui indique au navigateur la durée de vie de la ressource demandée

Il est à noter que les valeurs dans l’en-tête sont purement prescriptives, charge au navigateur de les respecter.

Lorsque le navigateur refait une requête au serveur pour le fichier index.html, il adjoindra également des en-têtes à sa requête. L’en-tête ETAG permettra de répondre au navigateur par un statut 304 NOT MODIFIED, dans le cas ou le fichier n’aurait pas changé.

L’en-tête date d’expiration permet au navigateur d’éviter d’envoyer des requêtes au serveur : tant que la date d’expiration n’est pas atteinte, le navigateur utilise la copie en cache.

Les templates AngularJS sont chargés au travers du service $http, qui se base sur xmlHttpRequest, qui lui même bénéficie du cache du navigateur.

Imaginons le scénario suivant :

  • le matin a 8h, le navigateur du client A demande le fichier index.html
  • le serveur lui renvoie, avec un ETAG “ABCD”, et une date d’expiration fixée au lendemain 8h
  • lors de l’utilisation de l’application, le navigateur va se baser sur la version en cache du fichier, tant que la date d’expiration est atteinte
  • a 12h, une nouvelle version de l’application est mise en ligne

Jusqu’au lendemain 8h, le client A utilisera une version potentiellement obsolète du fichier index.html.

Pour eviter cela, je vais mettre en place un mécanisme permettant d’assurer le bon fonctionnement du cache, tout en utilisant les derniers fichiers disponibles de l’application.

Dans un premier temps, il faut fournir un numéro identifiant l’application déployée. J’ai choisi d’utiliser la date de création d’un assembly du projet Web. Pour cela, j’ai intégré une simple balise script déclarant une constante AngularJs :

<script type="text/javascript">
    angular.module('SampleApplication.Config', [])
        .constant('SampleApplicationVersion', '<%: Version %>');
</script>

La suite de l’implémentation est aisée, grâce au système de HttpInterceptors fourni par AngularJs. Ces HttpInterceptors permettent de modifier les requêtes envoyées et reçues par le service $http.

.factory('SmartCacheInterceptor', ['$q', 'SampleApplicationVersion', function ($q, SampleApplicationVersion) {
    return {
        request: function (config) {
            if (config.url.indexOf(".htm") > -1) {
                var separator = config.url.indexOf("?") === -1 ? "?" : "&";
                config.url = config.url + separator + "v=" + SampleApplicationVersion;
            }
            return config || $q.when(config);
        }
    };
}]);

Voici les résultats dans fiddler. Les requêtes sur l’url “/” correspondent a un rafraichissement de la page du navigateur. Le serveur web utilisé est IIS Express, sans paramétrage spécifique.

image

Le code source est disponible sur github.

Indicateur de chargement AngularJs

Depuis la version 1.2.0 de AngularJs, le service $resource retourne des promise lors des appels aux méthodes get, query, save... Ceci ouvre des possibilités intéressantes, notamment la mise en place rapide d'indication de chargement.

Pour ce faire, j'ai choisi d'implémenter une directive, afin de pouvoir déclarer mon Loader ainsi :

<div loader="data">
    {{data | json}}
</div>

Ceci va donc orienter la déclaration de la directive, pour utiliser la transclusion et un scope isolé :

.directive('loader', ['$q', function ($q) {
    return {
        transclude: true,
        templateUrl: 'app/loader/loader.html',
        scope: {
            source: '=loader'
        },
        link: function (scope, elem, attrs) {
            
        }
    }
}])

Ensuite il faut écrire la fonction link pour réagir aux évènements du promise :

.directive('loader', ['$q', function ($q) {
    return {
        transclude: true,
        templateUrl: 'app/loader/loader.html',
        scope: {
            source: '=loader'
        },
        link: function (scope, elem, attrs) {
            scope.$watch("source", function (val) {
                scope.status = 0;
                val.$promise.then(function (success) {
                    scope.status = 200;
                }, function (err) {
                    scope.status = err.status;
                });
            });
        }
    }
}])

Le $watch permet de réagir à une assignation de la valeur en chargement, notamment lors de l'appel à une fonction de rechargement de données. Pour obtenir une référence sur l’objet promise renvoyé par $resource, il faut passer par la propriété $promise de celui-ci

Enfin, pour afficher tout ça, il nous faut un template :

<div>
    <div ng-hide="status==200"  ng-switch="status">
        <div ng-switch-when="0">
            <span><i class="fa fa-2x fa-spin fa-spinner"></i> Loading</span>
        </div>
        <div ng-switch-default>
            <span>Error from server : {{status}}</span>
        </div>
    </div>
    <div ng-show="status==200" ng-transclude></div>
</div>

Le code est disponible sur github .

De 0 a un package Nuget

En préparant un projet de démo AngularJS + Bootrap + Web Api (article a venir !), j’ai eu besoin de générer des données de test. Après quelques recherches infructueuses, j’ai donc choisi de monter mon petit projet. Ca tombe bien, j’avais envie de coder un petit projet “utilitaire”, et de me faire plaisir avec une API de type “Fluent”.

Une première version est dores et déjà disponible sur github : https://github.com/mathieubrun/Cogimator.SampleDataGenerator. J’aurais l’occasion de vous en reparler dans un article à venir (ca commence a faire beaucoup d’articles à venir…).

Apres l’avoir référencé “manuellement” dans mon projet de démo AngularJS, je me suis demandé comment optimiser le déploiement de mon générateur de données. Ca tombe bien, j’avais envie de créer un package nuget.

 

Etape 1 : création du projet

Rien d’exceptionnel, une solution Visual Studio standard, avec l’option “Activer la restauration des packages nuget” activée, ceci afin d’avoir un nuget.exe accessible facilement en ligne de commande depuis le dossier de la solution.

Etape 2 : création d’un compte Nuget.org

Ici, toujours rien de complètement fou, on s’enregistre, et on note bien l’api Key, pour l’activer sur le poste de développement.

 

Etape 3 : création du fichier .nuspec

Ici, les choses peuvent un peu se compliquer, selon le temps que l’on souhaite consacrer à la documentation Nuget. Pour faire simple, il faut initialiser un fichier .nuspec avec la commande suivante

.nuget\nuget.exe spec

Ensuite, il faut déplacer ce fichier dans le dossier du projet qui va servir a générer le package, et lui donner le même nom que le .csproj :

image

Enfin, il ne reste plus qu’a modifier le contenu du fichier nuspec pour l’adapter a votre projet.

Etape 4 : publication manuelle

La création du package est maintenant possible, avec la commande

.nuget\nuget.exe pack Cogimator.SampleDataGenerator\Cogimator.SampleDataGenerator.csproj

Nuget s’appuiera sur les .csproj et .nuspec pour générer le package.

Il ne reste plus qu’a l’uploader sur nuget.org.

Etape 5 : le serveur d’intégration continue

J’ai choisi TeamCity pour remplir ce rôle, principalement en raison de sa simplicité, et aussi parce qu’il est développé par JetBrains (auteurs de ReSharper, donc pas vraiment des amateurs).

Je ne rentre pas dans les détails de l’installation, du type next, next, next. Comme j’exécute TeamCity sur mon poste de travail, J’ai juste changé le compte d’exécution des services pour “System account”. Sur un serveur dédié j’aurais crée un user spécifique (car c’est très mal de faire tourner des services avec System account !)

Une fois le serveur installé, il ne reste plus qu’a se rendre sur l’adresse http://localhost (avec le port spécifique si vous l’avez configuré ainsi) pour finaliser l’installation.

Ensuite, il faut créer un nouveau projet, avec un configuration associée. Ici, les valeurs par défaut feront très bien l’affaire.

Une fois le projet créer, il faut lui associer un contrôle de code source. TeamCity supporte git nativement, l’url présente sur la home de votre projet doit être reprise :

image

Petite subtilité, j’ai repris l’exécutable fourni avec le client github (dans ) et je l’ai copié dans un dossier spécifique :

image

Les étapes de build sont également aisées à configurer (je repense au temps passé a pondre du xml pour CruiseControl.NET…). La première étape est la compilation. Si vous avez bien paramétré le contrôle de code source, vous pouvez déjà choisir le fichier .sln avec le picker a droite du champ “solution file path” :

image

Nous arrivons maintenant aux étapes concernant nuget. L’ajout d’une tache “Nuget pack” vous aménera a l’écran suivant :

image

Ici, il faut bien penser à aller dans “Nuget settings” pour télécharger un client nuget pour TeamCity. Il est également possible de publier les packages nuget dans un feed local au serveur teamcity. Ce qui peut s’avérer plutôt pratique pour les développements en entreprise… Il est aussi à noter que le package sera généré avec un numéro de version correspondant par défaut au numéro de build. Ce qui s’avère pratique pour les mises à jour du package.

Enfin, la publication se fait avec la tache “Nuget publish” :

image

Une fois le build complété avec succès, on peut vérifier la présence du package sur nuget.org :

image

Améliorations matérielles

Après quelques dizaines d’heures d’impressions, j’ai eu un souci avec le charriot Y : celui-ci ne bougeait plus. La vis qui bloque la poulie sur l’axe du moteur s’était desserrée,  et l'axe tournait donc dans le vide.

J’ai donc resserré celle-ci, et je l’ai fixée avec un point de cyanoacrylate. Cette solution ne me satisfaisant qu’à moitié, j’ai commandé des courroies T2.5 ainsi que les poulies en aluminium correspondantes.

Une fois le montage et la calibration effectuée, je n’ai pas remarqué d’amélioration notable dans la qualité des impressions. Mais au moins, les poulies sont en métal et ne se sont plus desserrées depuis…

La surface d’impression

Comme vu précédemment, la planéité de la surface d’impression joue un rôle très important dans la qualité des impressions. Les imprécisions de la première couche se propageant aux couches suivantes.

Avec le PLA translucide fourni, je n’ai pas eu de soucis d’adhérence, en imprimant à 175/65 (extrudeur/lit).

Par contre avec du PLA de couleur, les soucis sont vites arrivés : même en imprimant la 1ere couche a 5mm/s, et quelle que soit la hauteur initiale, la pièce se détachait au bout de quelques couches. D’après le fournisseur du PLA (http://www.imprimante3dfrance.com/), la température conseillée est de 210/105. Mon plateau chauffant ayant du mal a dépasser les 80°C, 105°C me paraissaient vraiment beaucoup.

Après de nombreux essais sur du scotch bleu plus ou moins chauffé, un camarade a eu l’idée de poncer la plaque de verre, ce qui a résolu tous les soucis d’adhérence.

Premières calibrations de votre imprimante

D’apres le calculateur de Josef Prusa vous pouvez commencer avec les commandes suivantes :

M92 X64 ; calibrate X
M92 Y64 ; calibrate Y
M92 Z2267.72; calibrate Z
M92 E533 ; calibrate E

Ces commandes sont a placer dans la section “Start GCode” de slic3r :

image

L’extrudeur

La première chose à faire est de bien calibrer votre extrudeur. Sur une extrusion de 10cm, l'erreur doit être en dessous des 0.5 mm !

Dans un premier temps, dans l’onglet “Controle manuel” de Repetier, envoyer la commande “M92 E533” et extruder ensuite 10mm :

image

Ensuite, placer un repère sur le fil a environ 10cm (un bout de scotch fera bien l’affaire), et mesurer. Pour la mesure, j'utilise un pied a coulisse numérique, posé sur le guide fil de l’extrudeur, pour le stabiliser :

C360_2013-06-27-22-05-40-183

Ensuite, extruder 80mm, soit en une fois, ou bien en demandant plusieurs extrusions de 10mm. Pensez bien a attendre la fin d’une extrusion avant de cliquer a nouveau sur le bouton. Sinon, l’imprimante peut réagir de manière étrange.

Une fois l’extrusion finie, mesurez a nouveau. Si la différence entre la mesure avant extrusion et la mesure après ne correspond pas à 80mm, faites un produit en croix pour obtenir la nouvelle valeur M92 Exxx a envoyer. Envoyer la nouvelle valeur, extrudez 10mm, et recommencez les mesures jusqu’à obtenir un résultat avec moins de 0.5% de marge.

Les axes X et Y

Pour calibrer les axes X et Y, j'ai utilisé ce modele thingiverse. Une fois les axes calibrés j'obtiens une précision en dessous du 1/10e de mm!

C360_2013-06-27-22-07-48-825

L’axe Z

Pour l’axe Z, j’ai laissé la valeur par défaut du calculateur.

IIS Express Here

Parfois, pour tester rapidement un exemple d’application JS, il peut être utile de pouvoir lancer rapidement un serveur web. Entre en scene IIS Express, et sa ligne de commande… Encore mieux, un petit fichier de registre a appliquer, pour pouvoir lancer IIS Express depuis l’explorateur windows : https://github.com/chrismcabz/IIS-Express-Here. Et quand on a fini de tester, il y a juste a fermer la console ouverte… Pratique.

Imprimante 3D, les premiers pas

Le montage

Une Printrbot LC V2, en kit, vous la recevrez sous cette forme :

C360_2013-04-24-20-43-12-044

Un ensemble de sachets, de vis, de pièces en bois découpées au laser. Comptez au moins 6h pour le montage. La documentation indique 4h, mais c'est pour quelqu'un qui conçoit et qui monte des imprimantes 3D toute la journée.

Lors du montage, il s'agira de bien serrer toutes les vis, sans casser le bois, pour qu'il n'y ait aucun jeu entre les pièces fixes. Attention aux axes X, ne pas les verrouiller avant d'avoir bien vérifié que le charriot coulisse bien.

Pour les courroies, il faut les tendre suffisamment pour qu'elles produisent une note grave quand on les pince comme une corde de guitare. Pas trop de tension non plus, sinon vous allez endommager l'axe du moteur ou la courroie !

Premier lancement

Avant de brancher la machine, pensez a bien vérifier que les vis des endstop X Y et Z sont bien réglées. Si elles n'activent pas les interrupteurs lorsque le charriot ou le plateau arrive en butée, vous risquez d'endommager la machine.

Le plateau

La chose la plus importante pour une impression réussie est un plateau bien plan. Une plaque de verre (0.4mm d'épaisseur) aux bonnes dimensions, et fixé avec des pinces fera l'affaire :

C360_2013-04-30-21-35-14-684

Dans le cas de la PrintrBot LC V2, j'ai du couper les pinces, sinon le charriot butait dedans :

C360_2013-04-30-21-36-23-565

Pour les fixer, des pines a circlip seront d'une grande aide:

C360_2013-04-30-21-38-45-450

Maintenant, il va falloir régler la hauteur de chaque coin du plateau pour que la tête d'impression soit toujours a la même hauteur.
Pour cela, j'utilise des jauges d'épaisseur, et je règle la hauteur sur 0.15mm. Histoire de vous éviter des surprises, il vaut mieux faire ce réglage avec l'extrudeur et le plateau a température : 180° et 60° respectivement, pour du PLA.
Pourquoi 0.15 ? Parce qu'a cette épaisseur, la jauge est assez souple. Ensuite, dans slic3r, vous avez une option pour "baisser" le z. En effet, slic3r considère le z calibré a 0. Donc  une des premières choses de faites dans le GCode est de placer la buse a la valeur z correspondant au layer height, par défaut 0.30. Donc dans notre cas, le z se trouvera a 0.45 mm du plateau au début de l'impression. Bien trop haut. Je l'ai compensé par un adjust z de -0.15. Donc la tête se trouvera a z = 0.30 mm du plateau.

Implémentation naïve d'un Serializer en C# - Partie 7

La dernière ligne droite dans l’implémentation de ce Serializer était d’avoir des performances similaires (ou meilleures!) à celles du BinaryFormatter du Framework .Net. En utilisant uniquement la reflection, ce n’était pas gagné d’avance.

foreach (var prop in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public).OrderBy(x => x.Name))
{
    var v = this.DeserializeBase(prop.FieldType, destination, source);

    prop.SetValue(destination, v);
}

En effet, a chaque serialisation de la meme classe, ce code va récupérer les champs, encore et encore, ce qui n'est de loin pas efficace.

L’optimisation que j’ai choisi a été de remplacer l’utilisation de la reflection par la création de LambdaExpression basées sur les champs des objets a sérialiser.

gettersForType = new List<Tuple<Type, Func<object, object>>>();

// create getter list from fields
foreach (var prop in sourceType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public))
{
    var getters = CreateGetter(prop);

    gettersForType.Add(Tuple.Create(prop.FieldType, getters));
}

Ici, la reflection sera utilisée une fois pour obtenir la liste des champs, et générer les Getters qui permettront d'en lire les valeurs.

La méthode CreateGetter va retourner un Func après avoir crée et compilé une LambdaExpression.

private static Func<object, object> CreateGetter(FieldInfo field)
{
    var fieldType = field.DeclaringType;

    // the input parameter for the lambda
    var sourceParameter = Expression.Parameter(typeof(object), "source");

    // as the parameter is of object type, a cast or conversion may be required
    var castedSource = GetExpressionAsTypeIfNeeded(sourceParameter, fieldType);

    // get field value
    var fieldExpression = Expression.Field(castedSource, field);

    // as the return parameter is of type object, a cast or conversion may be required
    var castedField = Expression.TypeAs(fieldExpression, typeof(object));

    // the lambda expression for accessing a field on an object
    var expr = Expression.Lambda<Func<object, object>>(castedField, sourceParameter);
    return expr.Compile();
}

Les éléments clés dans cette méthode sont :

  • La déclaration d’un paramètre pour la LambdaExpression, de type object, qui sera casté ou converti dans le bon type. En effet, comme on ne connait pas en avance le type de l’objet que l’on va désérialiser, la signature de la lambda expression est Func<object,object>.
  • La récupération d’une expression renvoyant la valeur du champ
  • Le cast de cette valeur en object pour la renvoyer.

De la même manière, j’avais implémenté une méthode CreateSetter, qui elle renvoyait un Action<object,object>. Cette méthode, créée a la volée pour un champ donné, prenait en paramètre l’objet en cours de désérialisation, ainsi que la valeur a assigner au champ.

private static Action<object, object> CreateSetter(FieldInfo field)
{
    var fieldType = field.DeclaringType;

    // the input parameter for the lambda
    var destinationParameter = Expression.Parameter(typeof(object), "destination");
    var valueParameter = Expression.Parameter(typeof(object), "fieldValue");

    var castedDestination = GetExpressionAsTypeIfNeeded(destinationParameter, fieldType);
    var castedValue = GetExpressionAsTypeIfNeeded(valueParameter, field.FieldType);

    // get field value
    var fieldExpression = Expression.Field(castedDestination, field);

    // as the return parameter is of type object, a cast or conversion may be required
    var assign = Expression.Assign(fieldExpression, castedValue);

    // the lambda expression for accessing a field on an object
    var expr = Expression.Lambda<Action<object, object>>(assign, destinationParameter, valueParameter);
    return expr.Compile();
}

Le souci de cette méthode est quelle renvoie un Action<object,object>. Or si les types valeurs “basiques” comme int, long, float, ne posent pas de souci, comme ils sont gérés par un sérializer a part, les structs, qui sont également passés par valeurs, ne sont pas désérialisés !

En effet, la méthode va agir sur une copie de l’instance en cours de désérialisation, dont les champs garderont les valeurs par défaut.

J’ai donc choisi d’implémenter une méthode plus complète, qui va non seulement générer le code pour définir les valeurs des champs, mais également prendre en charge la création de l’instance qui va être désérialisée. Cette méthode renvoie une fonction, qui prend en paramètre le flux dans lequel lire les valeurs a désérialiser, l’instance de l’objet sur laquelle seront définies les champs, et qui retourne l’instance désérialisée.

Dans le cas des types valeurs, la fonction générée se charge de la création de l’instance, et la retourne. De ce fait, on ne souffre plus des problèmes posés par l’implémentation précédente.

private Func<ExtendedBinaryReader, object, object> CreateSetters(Type type)
{
    // the input parameters of the generated lambda : the destination instance on which the setters will be applied
    var destinationParameter = Expression.Parameter(typeof(object), "destination");

    // the BinaryReader from which to get the data
    var binaryReaderParameter = Expression.Parameter(typeof(ExtendedBinaryReader), "source");

    // a variable to hold the destination instance
    var deserializedType = Expression.Variable(type, "destination");

    var expressionBlock = new List<Expression>();

    if (!type.IsValueType)
    {
        // if the type is not a value type the instance given as a parameter is used, or a new instance is created
        var coalesce = Expression.Coalesce(GetExpressionAsTypeIfNeeded(destinationParameter, type), Expression.New(type));

        // the first "line" of the lambda is to assign the destination variable
        expressionBlock.Add(Expression.Assign(deserializedType, coalesce));
    }
    else
    {
        // for a value type, a "new" instance is created
        expressionBlock.Add(Expression.Assign(deserializedType, Expression.New(type)));
    }

    var thisAsMethodTarget = Expression.Constant(this);

    var methodToCall = typeof(FastDefaultObjectSerializer).GetMethod("DeserializeBase");
    var deserializedTypeAsObject = Expression.TypeAs(deserializedType, typeof(object));

    foreach (var field in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public))
    {
        // access to the field on the instance being deserialized
        var fieldExp = Expression.Field(deserializedType, field);

        var fieldType = Expression.Constant(field.FieldType);

        // a methood call expression
        var call = Expression.Call(
            thisAsMethodTarget,
            methodToCall,
            fieldType,
            deserializedTypeAsObject,
            binaryReaderParameter);

        // the result of the method call is converted to the field type if needed ...
        var callResultAsFieldType = GetExpressionAsTypeIfNeeded(call, field.FieldType);

        // ... and is assigned to the field
        var assignToField = Expression.Assign(fieldExp, callResultAsFieldType);

        expressionBlock.Add(assignToField);
    }

    // the return part of the lambda
    var returnTarget = Expression.Label(typeof(object));
    var returnExpression = Expression.Return(returnTarget, deserializedTypeAsObject, typeof(object));
    var returnLabel = Expression.Label(returnTarget, deserializedTypeAsObject);

    expressionBlock.Add(returnExpression);
    expressionBlock.Add(returnLabel);

    var block = Expression.Block(new ParameterExpression[] { deserializedType }, expressionBlock);

    var lambda = Expression.Lambda<Func<ExtendedBinaryReader, object, object>>(block, binaryReaderParameter, destinationParameter);

    return lambda.Compile();
}

Dans cette méthode, les points clés sont :

  • le test sur le type de l’objet a désérialiser : si il s’agit d’un type valeur, la méthode va le créer, et le retourner. Si non, la méthode utilisera l’instance passée en paramètre.
  • l’appel a la méthode DeserializeBase, afin de s’appuyer sur les mécanismes implémentés précédemment pour la désérialisation
  • la génération d’un bloc de code pour la désérialisation de chaque champ

Cette optimisation a permi de diviser par 3 le temps de serialisation/deserialisation (10000 iterations) d’une classe simple.

Comme toujours, le code est disponible sur github : https://github.com/mathieubrun/Cogimator.Serialization

Utilisation mémoire d’un processus .NET

Lorsque l’on ouvre le gestionnaire des taches Windows, on peut y voir un onglet “Mémoire (jeu de travail privé)” pour chaque processus. Mais que ce cache derrière ce chiffre ? Afin de le savoir, je vais vous présenter l’outil VMMap, de Sysinternals, disponible au téléchargement ici : http://technet.microsoft.com/en-us/sysinternals/dd535533.aspx

Cet outil va vous présenter trois graphes de mémoires :

  • Commited : représente la quantité qu’occuperaient tout le code et données de l’application, ainsi que les fichiers mappés par celle ci.
  • Private Bytes : représente la quantité de mémoire demandée par le processus, et ne pouvant être partagée avec d’autres processus. Cette mémoire peut se trouver sur un fichier d'échange.
  • Working Set : représente la mémoire physique utilisée par le processus, c’est à dire qu’aucun accès au fichier d’echange ne sera fait lors d’un accès à cette mémoire.

Ces trois graphes sont subdivisés en différentes catégories. Typiquement, les catégories sur lesquelles le développeur pourra avoir un impact sont :

  • Image : représente les librairies chargées par l'application.
  • Managed Heap : représente les tas alloués par la CLR .Net. Une augmentation incontrôlée de cette valeur peut indiquer une fuite mémoire.
  • Private : représente la mémoire non allouée par la CLR .Net. Par exemple,  les données d'une image chargée au travers de Bitmap.FromFile seront dans cette zone mémoire.

Voici quatre captures d'écran de l’outil VMMap,  représentant quatre états de la mémoire pour une application simple.

Après le chargement de l'application :

image

On peut constater que la mémoire “Managed Heap” ainsi que “Private Data” sont respectivement de 2.3 et 25 Mo.

Après création de 10 tableaux de 1000000 bytes (non initialisés) :

image

La partie “Managed Heap” dans “Private Bytes” est maintenant de 109 Mo. Il s’agit uniquement de mémoire réservée, et non de mémoire utilisée ! C’est bien pour cela que le “Managed Heap” dans le “Working Set” est toujours de 2.5Mo.

Après l'initialisation de ces tableaux :

image

Cette fois ci, la taille du “Managed Heap” dans le “Working Set” a augmenté de manière significative : 108 Mo.

Après chargement de 300 images png de 40Ko.

image

Les données des images ont été allouées dans “Private data”, car System.Drawing.Bitmap utilise du code non managé.

Enfin, pour suivre la consommation mémoire d’une application, on pourra se baser sur les compteurs de performance suivants :

image