Fuites de mémoire Python [fermé]

180

J'ai un script de longue durée qui, s'il s'exécute suffisamment longtemps, consommera toute la mémoire de mon système.

Sans entrer dans les détails du script, j'ai deux questions:

  1. Y a-t-il des «meilleures pratiques» à suivre, qui aideront à empêcher les fuites de se produire?
  2. Quelles techniques existe-t-il pour déboguer les fuites de mémoire en Python?
Fragsworth
la source
5
J'ai trouvé cette recette utile.
David Schein
Il semble imprimer beaucoup trop de données pour être utile
Casebash
1
@Casebash: Si cette fonction imprime quelque chose, vous le faites gravement mal. Il répertorie les objets avec la __del__méthode qui ne sont plus référencés sauf pour leur cycle. Le cycle ne peut pas être interrompu en raison de problèmes avec __del__. Répare le!
Helmut Grohne

Réponses:

83

J'ai essayé la plupart des options mentionnées précédemment mais j'ai trouvé que ce petit package intuitif était le meilleur: pympler

Il est assez simple de tracer des objets qui n'ont pas été récupérés, consultez ce petit exemple:

installer le package via pip install pympler

from pympler.tracker import SummaryTracker
tracker = SummaryTracker()

# ... some code you want to investigate ...

tracker.print_diff()

La sortie vous montre tous les objets qui ont été ajoutés, plus la mémoire qu'ils ont consommée.

Exemple de sortie:

                                 types |   # objects |   total size
====================================== | =========== | ============
                                  list |        1095 |    160.78 KB
                                   str |        1093 |     66.33 KB
                                   int |         120 |      2.81 KB
                                  dict |           3 |       840 B
      frame (codename: create_summary) |           1 |       560 B
          frame (codename: print_diff) |           1 |       480 B

Ce package fournit un certain nombre de fonctionnalités supplémentaires. Consultez la documentation de pympler , en particulier la section Identifier les fuites de mémoire .

linqu
la source
5
Il convient de noter que cela pymplerpeut être LENT . Si vous faites quelque chose en semi-temps réel, cela peut complètement paralyser les performances de votre application.
Fake Name
@sebpiq étrangement, la même chose m'arrive ... avez-vous une idée de pourquoi cela se produit? Un rapide coup d'œil au code source n'a donné aucune idée réelle.
linusg
25

Permettez-moi de recommander l' outil mem_top que j'ai créé

Cela m'a aidé à résoudre un problème similaire

Il montre juste instantanément les principaux suspects de fuites de mémoire dans un programme Python

Denis Ryzhkov
la source
1
c'est vrai ... mais cela donne très peu d'explications sur l'utilisation / les résultats
me_
@me_, cet outil contient des sections "Utilisation" et "Explication du résultat" documentées. Dois-je ajouter une explication comme "refs est le nombre de références de l'objet, types est le nombre d'objets de ce type, octets est la taille de l'objet" - ne serait-il pas trop évident de documenter cela?
Denis Ryzhkov
les documents d'utilisation de l'outil donnent une seule ligne disant "de temps en temps: logging.debug (mem_top ())", tandis que son explication des résultats est l'expérience réelle de suivi des erreurs de l'auteur sans contexte ... ce n'est pas une spécification technique qui dit un développeur exactement ce qu'il regarde ... Je ne frappe pas votre réponse ... cela montre les suspects de haut niveau comme facturés ... cela ne donne pas la documentation adéquate pour comprendre pleinement le résultat de l'utilisation ... par exemple , dans la sortie "Explaining Results", pourquoi le "GearmanJobRequest" est-il évidemment un problème? aucune explication pour pourquoi ...
moi_
1
Je suppose que je frappe par inadvertance votre outil, vous êtes l'auteur ... aucune infraction n'était prévue ...
moi_
6
@me_, je viens juste d'ajouter l'étape suivante à "Utilisation", j'ai ajouté la section "Compteurs", j'ai ajouté une explication pourquoi exactement Gearman était suspect dans cet exemple réel, j'ai documenté chaque paramètre facultatif de "mem_top ()" dans le code, et a téléchargé tout cela en tant que v0.1.7 - veuillez jeter un œil si quelque chose d'autre pourrait être amélioré. Je vous remercie! )
Denis Ryzhkov
18

Le module Tracemalloc a été intégré en tant que module intégré à partir de Python 3.4, et apparemment, il est également disponible pour les versions précédentes de Python en tant que bibliothèque tierce (je ne l'ai pas testé cependant).

Ce module est capable de sortir les fichiers et les lignes précis qui ont alloué le plus de mémoire. À mon humble avis, cette information est infiniment plus précieuse que le nombre d'instances allouées pour chaque type (ce qui finit par être beaucoup de tuples 99% du temps, ce qui est un indice, mais aide à peine dans la plupart des cas).

Je vous recommande d'utiliser le tracemalloc en combinaison avec de la pyrasite . 9 fois sur 10, exécuter l' extrait de code 10 dans une coque en pyrasite vous donnera suffisamment d'informations et d'indices pour réparer la fuite en 10 minutes. Pourtant, si vous ne parvenez toujours pas à trouver la cause de la fuite, la pyrasite-shell en combinaison avec les autres outils mentionnés dans ce fil vous donnera probablement plus d'indices. Vous devriez également jeter un coup d'œil à tous les assistants supplémentaires fournis par pyrasite (tels que la visionneuse de mémoire).

user1527491
la source
pytracemalloc.readthedocs.io n'existe plus
Dimitrios Mistriotis
12

Vous devriez particulièrement jeter un œil sur vos données globales ou statiques (données de longue durée).

Lorsque ces données augmentent sans restriction, vous pouvez également rencontrer des problèmes en Python.

Le garbage collector ne peut collecter que des données qui ne sont plus référencées. Mais vos données statiques peuvent raccorder des éléments de données qui doivent être libérés.

Un autre problème peut être les cycles de mémoire, mais au moins en théorie, le garbage collector devrait trouver et éliminer les cycles - du moins tant qu'ils ne sont pas accrochés à des données de longue durée.

Quels types de données de longue durée sont particulièrement gênants? Jetez un œil à toutes les listes et dictionnaires - ils peuvent grandir sans aucune limite. Dans les dictionnaires, vous pourriez même ne pas voir le problème venir car lorsque vous accédez aux dictionnaires, le nombre de clés dans le dictionnaire peut ne pas être très visible pour vous ...

Juergen
la source
7

Pour détecter et localiser les fuites de mémoire pour les processus de longue durée, par exemple dans les environnements de production, vous pouvez désormais utiliser stackimpact . Il utilise tracemalloc en dessous. Plus d'informations dans cet article .

entrez la description de l'image ici

logix
la source
4

En ce qui concerne les meilleures pratiques, gardez un œil sur les fonctions récursives. Dans mon cas, j'ai rencontré des problèmes de récursivité (là où il n'y en avait pas besoin). Un exemple simplifié de ce que je faisais:

def my_function():
    # lots of memory intensive operations
    # like operating on images or huge dictionaries and lists
    .....
    my_flag = True
    if my_flag:  # restart the function if a certain flag is true
        my_function()

def main():
    my_function()

fonctionner de cette manière récursive ne déclenchera pas le ramasse-miettes et n'effacera pas les restes de la fonction, donc à chaque fois que l'utilisation de la mémoire augmente et augmente.

Ma solution était de retirer l'appel récursif de my_function () et d'avoir main () gérer le moment de le rappeler. de cette façon, la fonction se termine naturellement et se nettoie après elle-même.

def my_function():
    # lots of memory intensive operations
    # like operating on images or huge dictionaries and lists
    .....
    my_flag = True
    .....
    return my_flag

def main():
    result = my_function()
    if result:
        my_function()
The4thIceman
la source
7
L'utilisation de la récursivité de cette manière sera également interrompue si vous atteignez la limite de profondeur de récursivité car Python n'optimise pas les appels de queue. Par défaut, il s'agit de 1000 appels récursifs.
Lie Ryan
3

Je ne suis pas sûr des «meilleures pratiques» pour les fuites de mémoire en python, mais python devrait effacer sa propre mémoire par son ramasse-miettes. Donc, je commencerais principalement par vérifier la liste circulaire de certains courts, car ils ne seront pas ramassés par le ramasse-miettes.

martiert
la source
3
ou des références à des objets qui sont conservés pour toujours, etc.
matt b
3
Pouvez-vous s'il vous plaît fournir des exemples de listes circulaires et d'objets qui sont conservés pour toujours?
Daniel
2

Ce n'est en aucun cas un conseil exhaustif. Mais la première chose à garder à l'esprit lors de l'écriture dans le but d'éviter de futures fuites de mémoire (boucles) est de s'assurer que tout ce qui accepte une référence à un rappel doit stocker ce rappel comme une référence faible.

Dmitry Rubanovich
la source