160 votes

Libération de la mémoire en Python

J'ai quelques questions connexes concernant l'utilisation de la mémoire dans l'exemple suivant.

  1. Si j'exécute dans l'interpréteur,

    foo = ['bar' for _ in xrange(10000000)]

    la mémoire réelle utilisée sur ma machine va jusqu'à 80.9mb . Moi alors,

    del foo

    La mémoire réelle diminue, mais seulement pour 30.4mb . L'interpréteur utilise 4.4mb baseline alors quel est l'avantage de ne pas divulguer 26mb de mémoire au système d'exploitation ? Est-ce parce que Python "anticipe", en pensant que vous pourriez utiliser cette quantité de mémoire à nouveau ?

  2. Pourquoi libère-t-il 50.5mb en particulier - sur quel montant se base la libération ?

  3. Existe-t-il un moyen de forcer Python à libérer toute la mémoire qui a été utilisée (si vous savez que vous n'utiliserez plus cette quantité de mémoire) ?

NOTE Cette question est différente de Comment puis-je libérer explicitement de la mémoire en Python ? car cette question traite principalement de l'augmentation de l'utilisation de la mémoire à partir de la ligne de base, même après que l'interpréteur ait libéré les objets via le garbage collection (avec l'utilisation de l'option gc.collect ou pas).

8 votes

Il convient de noter que ce comportement n'est pas spécifique à Python. En général, lorsqu'un processus libère de la mémoire allouée au tas, la mémoire n'est pas restituée au système d'exploitation avant la mort du processus.

0 votes

Votre question demande plusieurs choses, dont certaines sont inutiles, d'autres sont inappropriées pour l'OS, et d'autres encore pourraient être de bonnes questions. Demandez-vous si Python ne libère pas de mémoire, dans quelles circonstances exactes il peut/ne peut pas le faire, quel est le mécanisme sous-jacent, pourquoi il a été conçu de cette façon, s'il existe des solutions de contournement, ou quelque chose d'entièrement différent ?

4 votes

@abarnert J'ai combiné des sous-questions qui étaient similaires. Pour répondre à vos questions : Je sais que Python libère une partie de la mémoire au système d'exploitation, mais pourquoi pas toute la mémoire et pourquoi la quantité qu'il libère. S'il y a des circonstances où il ne peut pas le faire, pourquoi ? Quelles sont les solutions de contournement ?

155voto

abarnert Points 94246

Je suppose que la question qui t'intéresse vraiment ici est :

Existe-t-il un moyen de forcer Python à libérer toute la mémoire qui a été utilisée (si vous savez que vous n'utiliserez plus cette quantité de mémoire) ?

Non, il n'y en a pas. Mais il existe une solution de contournement facile : les processus enfants.

Si vous avez besoin de 500 Mo de stockage temporaire pendant 5 minutes, mais qu'après cela, vous devez continuer à fonctionner pendant 2 heures et que vous ne toucherez plus jamais à cette quantité de mémoire, créez un processus enfant pour effectuer le travail nécessitant beaucoup de mémoire. Lorsque le processus enfant disparaît, la mémoire est libérée.

Ce n'est pas complètement trivial et gratuit, mais c'est plutôt facile et bon marché, ce qui est généralement suffisant pour que l'échange en vaille la peine.

Tout d'abord, la façon la plus simple de créer un processus enfant est avec concurrent.futures (ou, pour les versions 3.1 et antérieures, l'option futures backport sur PyPI) :

with concurrent.futures.ProcessPoolExecutor(max_workers=1) as executor:
    result = executor.submit(func, *args, **kwargs).result()

Si vous avez besoin d'un peu plus de contrôle, utilisez la fonction multiprocessing module.

Les coûts sont les suivants :

  • Le démarrage des processus est plutôt lent sur certaines plateformes, notamment Windows. Nous parlons ici de millisecondes, pas de minutes, et si vous faites tourner un enfant pour effectuer un travail de 300 secondes, vous ne le remarquerez même pas. Mais ce n'est pas gratuit.
  • Si la grande quantité de mémoire temporaire que vous utilisez est vraiment grand site En faisant cela, votre programme principal peut être remplacé. Bien sûr, vous gagnez du temps à long terme, car si la mémoire restait en place pour toujours, il faudrait qu'il y ait un swapping à un moment donné. Mais cela peut transformer une lenteur progressive en un retard très perceptible d'un seul coup (et très tôt) dans certains cas d'utilisation.
  • L'envoi de grandes quantités de données entre les processus peut être lent. Encore une fois, si vous envoyez plus de 2K d'arguments et recevez 64K de résultats, vous ne le remarquerez même pas, mais si vous envoyez et recevez de grandes quantités de données, vous voudrez utiliser un autre mécanisme (un fichier, mmap ou autre ; les API de mémoire partagée dans les multiprocessing ; etc.).
  • L'envoi de grandes quantités de données entre les processus signifie que les données doivent être capturables (ou, si vous les collez dans un fichier ou une mémoire partagée, struct -able ou idéalement ctypes -).

0 votes

Très belle astuce, même si elle ne résout pas le problème :( Mais j'aime vraiment ça.

0 votes

C'est la seule solution qui ait fonctionné pour moi, car l'utilisation de la fonction del y gc.collect() ne l'a pas fait.

101voto

eryksun Points 10251

La mémoire allouée sur le tas peut faire l'objet de surenchères. Ceci est compliqué par les optimisations internes de Python pour l'allocation de petits objets ( PyObject_Malloc ) dans des pools de 4 KiB, classés pour des tailles d'allocation à des multiples de 8 octets -- jusqu'à 256 octets (512 octets en 3.3). Les pools eux-mêmes sont dans des arènes de 256 KiB, donc si un seul bloc dans un pool est utilisé, l'arène entière de 256 KiB ne sera pas libérée. Dans Python 3.3, l'allocateur de petits objets est passé à l'utilisation de cartes mémoire anonymes au lieu du tas, ce qui devrait améliorer la libération de la mémoire.

De plus, les types intégrés maintiennent des listes libres d'objets précédemment alloués qui peuvent ou non utiliser l'allocateur de petits objets. Le site int maintient une liste libre avec sa propre mémoire allouée, et la vider nécessite d'appeler PyInt_ClearFreeList() . On peut l'appeler indirectement en faisant un gc.collect .

Essayez comme ceci, et dites-moi ce que vous obtenez. Voici le lien pour psutil.Process.memory_info .

import os
import gc
import psutil

proc = psutil.Process(os.getpid())
gc.collect()
mem0 = proc.memory_info().rss

# create approx. 10**7 int objects and pointers
foo = ['abc' for x in range(10**7)]
mem1 = proc.memory_info().rss

# unreference, including x == 9999999
del foo, x
mem2 = proc.memory_info().rss

# collect() calls PyInt_ClearFreeList()
# or use ctypes: pythonapi.PyInt_ClearFreeList()
gc.collect()
mem3 = proc.memory_info().rss

pd = lambda x2, x1: 100.0 * (x2 - x1) / mem0
print "Allocation: %0.2f%%" % pd(mem1, mem0)
print "Unreference: %0.2f%%" % pd(mem2, mem1)
print "Collect: %0.2f%%" % pd(mem3, mem2)
print "Overall: %0.2f%%" % pd(mem3, mem0)

Sortie :

Allocation: 3034.36%
Unreference: -752.39%
Collect: -2279.74%
Overall: 2.23%

Edit :

Je suis passé à une mesure relative à la taille de la VM du processus pour éliminer les effets des autres processus du système.

Le runtime C (par exemple glibc, msvcrt) réduit le tas lorsque l'espace libre contigu au sommet atteint un seuil constant, dynamique ou configurable. Avec la glibc, vous pouvez régler cela avec mallopt (M_TRIM_THRESHOLD). Dans ces conditions, il n'est pas surprenant que la taille du tas soit plus importante, voire beaucoup plus importante, que le bloc que vous avez utilisé pour le calcul de la taille. free .

En 3.x range ne crée pas une liste, donc le test ci-dessus ne créera pas 10 millions de int objets. Même si c'était le cas, le int en 3.x est fondamentalement un type 2.x long qui n'implémente pas de liste de freelance.

2 votes

Utilice memory_info() au lieu de get_memory_info() y x est défini

0 votes

Vous obtenez bien 10^7 int s même en Python 3, mais chacun remplace le dernier dans la variable de la boucle, donc ils n'existent pas tous tout de suite.

0 votes

J'ai rencontré un problème de fuite de mémoire, et je suppose que la raison est ce que vous avez répondu ici. Mais comment puis-je prouver mon hypothèse ? Existe-t-il un outil qui peut montrer que de nombreux pools sont malloqués, mais que seul un petit bloc est utilisé ?

37voto

abarnert Points 94246

Eryksun a répondu à la question n°1, et j'ai répondu à la question n°3 (la n°4 originale), mais répondons maintenant à la question n°2 :

Pourquoi libère-t-il 50.5mb en particulier - sur quoi est basée la quantité libérée ?

Ce qui est basé sur, finalement, toute une série de coïncidences à l'intérieur de Python et malloc qui sont très difficiles à prévoir.

Tout d'abord, selon la façon dont vous mesurez la mémoire, il se peut que vous ne mesuriez que les pages effectivement mappées en mémoire. Dans ce cas, chaque fois qu'une page est échangée par le pager, la mémoire apparaîtra comme "libérée", même si elle n'a pas été libérée.

Ou bien vous mesurez les pages en cours d'utilisation, qui peuvent ou non compter les pages allouées mais jamais touchées (sur les systèmes qui surallouent de manière optimiste, comme Linux), les pages allouées mais marquées MADV_FREE etc.

Si vous mesurez réellement les pages allouées (ce qui n'est en fait pas très utile, mais il semble que ce soit ce que vous demandez), et que les pages ont réellement été désallouées, il y a deux circonstances dans lesquelles cela peut se produire : Soit vous avez utilisé brk ou équivalent pour rétrécir le segment de données (très rare de nos jours), ou vous avez utilisé munmap ou similaire pour libérer un segment cartographié. (Il existe aussi théoriquement une variante mineure de cette dernière, en ce sens qu'il existe des moyens de libérer une partie d'un segment mappé - par exemple, le voler avec MAP_FIXED pour un MADV_FREE segment que vous démappez immédiatement).

Mais la plupart des programmes n'allouent pas directement des éléments à partir de pages de mémoire ; ils utilisent un système de gestion de l'information. malloc -allocateur de style. Lorsque vous appelez free l'allocateur ne peut libérer des pages pour le système d'exploitation que si vous êtes juste free dans le dernier objet vivant d'un mappage (ou dans les N dernières pages du segment de données). Il n'y a aucun moyen pour votre application de prédire raisonnablement cette situation, ni même de détecter qu'elle s'est produite à l'avance.

CPython rend les choses encore plus compliquées : il dispose d'un allocateur d'objets personnalisé à deux niveaux au-dessus d'un allocateur de mémoire personnalisé au-dessus de malloc . (Voir les commentaires de la source pour une explication plus détaillée). Et pour couronner le tout, même au niveau de l'API C, et encore moins en Python, vous ne contrôlez même pas directement le moment où les objets de haut niveau sont désalloués.

Ainsi, lorsque vous libérez un objet, comment savez-vous s'il va libérer de la mémoire pour le système d'exploitation ? Eh bien, vous devez d'abord savoir que vous avez libéré la dernière référence (y compris toutes les références internes que vous ne connaissiez pas), permettant ainsi au GC de le désallouer. (Contrairement à d'autres implémentations, au moins CPython désallouera un objet dès qu'il y sera autorisé). Cela désalloue généralement au moins deux choses au niveau inférieur (par exemple, pour une chaîne de caractères, vous libérez la propriété PyString et le tampon de chaîne).

Si vous faire désallouer un objet, pour savoir si cela entraîne la désallocation d'un bloc de stockage d'objets par le niveau inférieur suivant, vous devez connaître l'état interne de l'allocateur d'objets, ainsi que la façon dont il est implémenté. (Cela ne peut évidemment pas se produire à moins que vous ne désallouiez le dernier objet du bloc, et même dans ce cas, cela peut ne pas se produire).

Si vous faire désallouer un bloc de stockage objet, pour savoir si cela provoque une free vous devez connaître l'état interne de l'allocateur PyMem, ainsi que la façon dont il est implémenté. (Encore une fois, vous devez être en train de désallouer le dernier bloc utilisé à l'intérieur d'un fichier malloc et même dans ce cas, cela peut ne pas se produire).

Si vous faire free a malloc ée, pour savoir si cela provoque un munmap ou équivalent (ou brk ), vous devez connaître l'état interne de la fonction malloc ainsi que la manière dont il est mis en œuvre. Et celle-ci, contrairement aux autres, est hautement spécifique à la plateforme. (Et encore une fois, vous devez généralement désallouer la dernière mémoire utilisée. malloc dans un mmap segment, et même dans ce cas, cela peut ne pas se produire).

Donc, si vous voulez comprendre pourquoi il a libéré exactement 50,5 mégaoctets, vous allez devoir le retracer de bas en haut. Pourquoi est-ce que malloc démapper 50.5mb de pages quand vous avez fait ces un ou plusieurs free (pour probablement un peu plus de 50.5mb) ? Vous devez lire les règles de votre plateforme. malloc puis parcourir les différents tableaux et listes pour voir son état actuel. (Sur certaines plateformes, il peut même utiliser des informations au niveau du système, ce qui est pratiquement impossible à capturer sans faire un instantané du système pour l'inspecter hors ligne, mais heureusement, ce n'est généralement pas un problème). Et ensuite, vous devez faire la même chose aux 3 niveaux supérieurs.

Donc, la seule réponse utile à la question est "Parce que".

À moins que vous ne fassiez du développement à ressources limitées (par exemple, embarqué), vous n'avez aucune raison de vous soucier de ces détails.

Et si vous sont dans le cadre d'un développement à ressources limitées, il est inutile de connaître ces détails ; il faut pratiquement contourner tous ces niveaux et se concentrer sur les aspects suivants mmap la mémoire dont vous avez besoin au niveau de l'application (éventuellement avec un allocateur de zone simple, bien compris et spécifique à l'application entre les deux).

4voto

de20ce Points 69

Tout d'abord, vous pouvez installer des regards :

sudo apt-get install python-pip build-essential python-dev lm-sensors 
sudo pip install psutil logutils bottle batinfo https://bitbucket.org/gleb_zhulik/py3sensors/get/tip.tar.gz zeroconf netifaces pymdstat influxdb elasticsearch potsdb statsd pystache docker-py pysnmp pika py-cpuinfo bernhard
sudo pip install glances

Ensuite, exécutez-le dans le terminal !

glances

Dans votre code Python, ajoutez au début du fichier, ce qui suit :

import os
import gc # Garbage Collector

Après avoir utilisé la "Grande" variable (par exemple : myBigVar) pour laquelle vous souhaitez libérer de la mémoire, écrivez dans votre code python ce qui suit :

del myBigVar
gc.collect()

Dans un autre terminal, exécutez votre code python et observez dans le terminal "glances", comment la mémoire est gérée dans votre système !

Bonne chance !

P.S. Je suppose que vous travaillez sur un système Debian ou Ubuntu

-2voto

sivi Points 501

Il me semble que la réponse à votre question est ici : http://docs.python.org/2/c-api/memory.html

void PyMem_Free(void *p)

Libère le bloc de mémoire pointé par p, qui doit avoir été retourné par un appel précédent à PyMem_Malloc() o PyMem_Realloc() . Sinon, ou si PyMem_Free(p) a été appelé avant, non défini comportement non défini. Si p est NULL, aucune opération n'est effectuée.

Les macros orientées type suivantes sont fournies par commodité. Notez que TYPE fait référence à n'importe quel type C.

TYPE* PyMem_New(TYPE, size_t n)

Même que PyMem_Malloc() mais attribue (n * sizeof(TYPE)) octets de mémoire. Renvoie un pointeur coulé vers TYPE*. La mémoire n'aura pas été initialisée d'aucune façon.

TYPE* PyMem_Resize(void *p, TYPE, size_t n)

Même que PyMem_Realloc() mais le bloc mémoire est redimensionné à (n * sizeof(TYPE)) des octets. Renvoie un pointeur coulé vers TYPE*. Au retour, p sera un pointeur vers la nouvelle zone de mémoire, ou NULL en cas de d'échec. Il s'agit d'une macro de préprocesseur C ; p est toujours réassigné. Sauvegarder la valeur originale de p pour éviter de perdre de la mémoire lors de la gestion des erreurs.

void PyMem_Del(void *p)

Même que PyMem_Free() .

En outre, les jeux de macros suivants sont fournis pour appeler la fonction allocateur de mémoire Python directement, sans faire appel aux fonctions de l'API énumérées ci-dessus. Cependant, notez que leur utilisation ne préserve pas compatibilité binaire entre les différentes versions de Python et est donc déprécié dans les modules d'extension.

PyMem_MALLOC() , PyMem_REALLOC() , PyMem_FREE() .

PyMem_NEW() , PyMem_RESIZE() , PyMem_DEL() .

Exemples sur la page d'origine

Outre les fonctions destinées à gérer les blocs de mémoire brute du tas Python, les objets en Python sont alloués et libérés avec PyObject_New(), PyObject_NewVar() et PyObject_Del().

pour que vous puissiez essayer*

foo.PyObject_New()

*Laissez-moi savoir si ça marche, c'est une nouvelle forme de try catch.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X