UKOnline

Module guppy

Une autre possibilité pour obtenir de l'information sur la consommation mémoire consiste à utiliser le module guppy. Ce dernier contient notamment un sous-module d'analyse du contenu du tas. Ce dernier, dénommé Heapy, est plutôt complexe à apprendre, mais il est néanmoins possible de facilement réaliser de simples analyses.

Analyse du tas

Pour effectuer une analyse du tas, il faut d'abord créer un contexte de session avec la fonction hpy. On peut ensuite créer une capture de tous les objets accessibles en RAM avec la méthode heap. Voici un simple exemple d'utilisation du module guppy :

En observant le résultat de l'exécution, on se rend compte que, sans avoir rien fait de particulier, il y a déjà une multitude d'objets créés en mémoire. Le résultat montre, par exemple, qu'il y a déjà 11 116 objets de type str en mémoire, ce qui représente 31 % de la mémoire totale utilisée, soit 997 255 octets des 4 570 543 octets utilisés.

On peut voir le même type d'information pour toute une série d'autres types d'objet, chaque ligne du résultat rassemblant précisément l'information de tous les objets d'un même type :

Partition of a set of 35935 objects. Total size = 4570543 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0  11116  31   997255  22    997255  22 str
     1   7081  20   491872  11   1489127  33 tuple
     2   2518   7   444728  10   1933855  42 types.CodeType
     3   4988  14   350056   8   2283911  50 bytes
     4    446   1   346896   8   2630807  58 type
     5   2327   6   316472   7   2947279  64 function
     6      2   0   262480   6   3209759  70 _io.BufferedWriter
     7    446   1   243480   5   3453239  76 dict of type
     8     97   0   164832   4   3618071  79 dict of module
     9      1   0   131240   3   3749311  82 _io.BufferedReader
<115 more rows. Type e.g. '_.more' to view.>

Pour mesurer la consommation de mémoire résultant de l'exécution d'une partie de programme uniquement, il faut réaliser une capture avant et après la partie de programme d'intérêt.

Néanmoins, contrairement au module tracemalloc, il n'est pas possible de facilement comparer deux captures avec le module guppy. Pour réaliser une telle analyse, ce qu'il faut donc faire, c'est réinitialiser la surveillance de la mémoire avec la méthode setrelheap. Ainsi, la prochaine capture réalisée contiendra uniquement les objets créés en mémoire à partir de l'appel à cette méthode jusque l'appel à la méthode heap.

L'exemple suivant illustre une telle analyse, de la consommation mémoire d'une partie de programme uniquement :

Comme on peut le voir sur le résultat de l'exécution, il y a exactement $100$ objets de type str, $100$ objets de type float, $2$ objets de type list et enfin $1$ objet de type types.FrameType, qui ont été créés dans le tas, suite à l'exécution des deux instructions qui ont créé les deux objets respectivement référencés par les variables data et labels :

Partition of a set of 203 objects. Total size = 9816 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0    100  49     5200  53      5200  53 str
     1    100  49     2400  24      7600  77 float
     2      2   1     1808  18      9408  96 list
     3      1   0      408   4      9816 100 types.FrameType

Contrairement aux analyses que l'on a pu faire dans les sections précédentes, les informations que l'on obtient avec le module guppy sont bien plus précises et concernent uniquement la mémoire allouée dans le tas, et pas ce qui se trouve dans la pile, comme les variables locales.

Relation d'équivalence

Par défaut, la méthode heap rassemble les objets en suivant la relation d'équivalence « Clodo » (Class or dict owner). Selon cette relation, tous les objets provenant de la même classe ou du même dictionnaire sont repris dans la même ligne du résultat.

D'autres relations d'équivalence peuvent être utilisées, notamment bytype qui rassemble les objets par type et byrcs qui les rassemble selon les types des objets ayant une référence vers eux. En repartant de la capture mémoire faite dans la section précédente, on peut en savoir plus sur les objets « conteneurs » avec :

La partition obtenue avec cette instruction montre que $200$ des objets du tas sont référencés par des objets de type list, que deux sont référencés par le dictionnaire du module et qu'un n'est pas référencé :

Partition of a set of 203 objects. Total size = 9816 bytes.
 Index  Count   %     Size   % Cumulative  % Referrers by Kind (class / dict of class)
     0    200  99     7600  77      7600  77 list
     1      2   1     1808  18      9408  96 dict of module
     2      1   0      408   4      9816 100 <Nothing>

Le dictionnaire de module, accessible avec la fonction globals, contient toutes les variables globales déclarées dans le module. C'est la raison pour laquelle les objets référencés par les variables data et labels y sont également référencés.

On peut raffiner l'analyse précédente en identifiant, par exemple, les types des objets référencés par des objets de type list, avec :

La partition obtenue avec cette instruction montre que, parmi les $200$ objets référencés par des listes et comme on avait déjà pu l'observer précédemment, il y a $100$ objets de type str et $100$ de type float :

Partition of a set of 200 objects. Total size = 7600 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0    100  50     5200  68      5200  68 str
     1    100  50     2400  32      7600 100 float

Il y a encore beaucoup d'autres possibilités permettant des analyses très poussées avec le module guppy, mais elles sortent du cadre de ce livre.