Ipython: Fuite de mémoire avec% matplotlib inline

Créé le 19 déc. 2014  ·  23Commentaires  ·  Source: ipython/ipython

Salut tout le monde

J'ai trouvé un problème. Lancez simplement le code et regardez la mémoire. Puis supprimez "% matplotlib inline" et relancez.

import matplotlib
import matplotlib.pyplot as plt
import matplotlib.ticker

%matplotlib inline

import os
import sys
import StringIO
import urllib, base64

from matplotlib import rcParams

rcParams['figure.figsize'] = (24, 6)
rcParams['figure.dpi'] = 150

OUTPUT_FILENAME = "Asd"

def printHTML(html):
    with open(OUTPUT_FILENAME, "a") as outputFile: outputFile.write(html if type(html) == str else html.encode('utf8') )

def friendlyPlot():

    figure = plt.Figure()
    ax = plt.subplot2grid((1,2), (0,0))

    ax.plot( range(1000), range(1000) )


    #plt.show() 
    fig = plt.gcf()

    imgdata = StringIO.StringIO()
    fig.savefig(imgdata, format='png')
    imgdata.seek(0)  # rewind the data
    image = imgdata.buf.encode('base64').replace('\n', '')
    printHTML('<img src="data:image/png;base64,{0}" /><br />'.format(image))
    plt.close('all')
    imgdata.close()

open(OUTPUT_FILENAME, 'w').close()

for i in range(500):
    friendlyPlot()
bug matplotlib

Commentaire le plus utile

Je soutiens qu'une solution à ce problème serait appréciée.

Tous les 23 commentaires

J'ai également rencontré ce bogue, existe-t-il un moyen d'obtenir des graphiques en ligne sans fuites de mémoire? Je ne veux pas lancer de processus séparés pour chaque tracé, car les tableaux sont assez grands.

Pouvez-vous vérifier cela lorsque l'utilisation de la mémoire augmente:

len(IPython.kernel.zmq.pylab.backend_inline.show._to_draw)

C'est une liste où les chiffres sont stockés. Ils ne devraient être là que temporairement, mais peut-être qu'ils s'accumulent sans être effacés.

len (IPython.kernel.zmq.pylab.backend_inline.show._to_draw) = 0

BTW, je trace en utilisant la méthode .plot() sur les dataframes pandas.

OK, tant pis pour cette théorie.

Il est possible que les pandas conservent également des données autour des parcelles en interne. Le rapport original n'implique pas de pandas, cependant.

Combien de mémoire chaque tracé supplémentaire semble-t-il ajouter?

ok, cela semble être mon cas, j'utilisais pandas 0.16.0, mais le problème est résolu dans master:

https://github.com/pydata/pandas/pull/9814

Grand merci. Laisser ouvert puisque le rapport original n'impliquait pas de pandas.

Cela peut être reproduit plus simplement:

import matplotlib
import matplotlib.pyplot as plt
import matplotlib.ticker

%matplotlib inline

import os
import sys
import StringIO
import urllib, base64

from matplotlib import rcParams

rcParams['figure.figsize'] = (24, 6)
rcParams['figure.dpi'] = 150



def friendlyPlot():
    fig, ax = plt.subplots()
    ax.plot(range(1000))
    fig.savefig('tmp.png')
    plt.close('all')


for i in range(500):
    friendlyPlot()

Cela ne fuit pas de mémoire, donc c'est quelque chose du côté IPython et non du côté pyplot (je pense).

import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt
import matplotlib.ticker



import os
import sys
import StringIO
import urllib, base64

from matplotlib import rcParams

rcParams['figure.figsize'] = (24, 6)
rcParams['figure.dpi'] = 150



def friendlyPlot():
    fig, ax = plt.subplots()
    ax.plot(range(1000))
    fig.savefig('tmp.png')
    plt.close('all')


for i in range(500):
    friendlyPlot()

@tacaswell Avec votre code de test IPython sur Windows 7 consomme ici environ 1,7 Go qui ne sont pas libérés par la suite. L'exécution avec un nombre d'itérations légèrement plus élevé entraîne une erreur de mémoire. C'est donc toujours un problème.

@asteppke Le premier ou le deuxième bloc?

@tacaswell Avec votre premier code de test ( %matplotlib inline ), la consommation de mémoire atteint 1,7 Go. En revanche, lors de l'utilisation du deuxième élément ( matplotlib.use('agg') ), l'utilisation de la mémoire oscille uniquement entre 50 Mo et 100 Mo.

Les deux tests sont exécutés avec Python 3.4 et le notebook IPython version 4.0.5.

J'ai joué un peu plus avec ça. Je remarque que si je relance la boucle for dans l'exemple de @tacaswell à quelques reprises, l'utilisation de la mémoire n'augmente pas - il semble que ce soit le nombre que vous créez dans une seule cellule qui compte. IPython conserve certainement une liste de toutes les figures générées dans la cellule pour le backend en ligne, mais cette liste est définitivement effacée après l'exécution de la cellule, ce qui ne fait pas chuter l'utilisation de la mémoire, même après avoir fait gc.collect() .

Notre code pourrait-il mal interagir avec quelque chose dans matplotlib? Je pensais que _pylab_helpers.Gcf semblait probable, mais cela ne semble pas tenir à quoi que ce soit.

J'ai essayé de saisir une référence à l'un des chiffres et d'appeler gc.get_referrers() dessus; à part la référence que j'avais dans user_ns, tous les autres ressemblaient à des objets mpl - vraisemblablement beaucoup d'entre eux sont dans des boucles de référence. À quel objet est-il le plus probable que quelque chose d'autre ferait référence de manière inappropriée?

Je laisse tomber ceci dans la «liste de souhaits». Nous voulons le corriger, mais pour le moment, nous ne savons pas comment faire des progrès supplémentaires dans l'identification du bogue, et je ne pense pas que cela vaille la peine de retarder les versions pour cela.

Quiconque peut progresser obtient des points brownie. Gâteau aussi.

Pas vraiment de progrès, mais la mémoire semble être perdue quelque part dans le noyau. L'appel de gc.collect() après ou à l'intérieur de la boucle n'aide pas non plus, et summary.print_(summary.summarize(muppy.get_objects())) ne trouve aucune des fuites de mémoire. La définition de tous les _N et _iN à None n'aide pas non plus. C'est vraiment mystérieux.

Je me suis également demandé si cela créait des objets non récupérables, mais ceux-ci devraient finir par gc.garbage quand il n'y a pas d'autres références à eux, et c'est toujours vide quand je le vois utiliser beaucoup de RAM.

Je pense que quelqu'un qui connaît ces choses va devoir utiliser des outils de niveau C pour localiser la mémoire qui ne se libère pas. Il n'y a aucune preuve que des objets Python supplémentaires soient maintenus en vie n'importe où.

Je soutiens qu'une solution à ce problème serait appréciée.

Nous savons, mais à l'heure actuelle, personne n'a trouvé la cause du bogue.

+1

+1

BTW, je rencontre toujours ce problème de temps en temps sur les derniers matplotlib, pandas, jupyter, ipython. Si quelqu'un connaît un débogueur qui peut aider à résoudre cette communication multi-processus, veuillez me le faire savoir.

Cela pourrait-il avoir quelque chose à voir avec le mécanisme de cache du navigateur?

Bonne pensée, mais je ne pense pas. C'est le processus d'IPython qui prend de la mémoire supplémentaire, pas le navigateur, et
La reproduction de @tacaswell n'implique pas l'envoi de tracés au navigateur.

Salut, je crois avoir trouvé une partie du coupable et un moyen de réduire considérablement, mais pas complètement, ce problème!

Après avoir parcouru le code ipykernel/pylab/backend_inline.py , j'ai eu l'impression que le mode interactif stockait beaucoup de "trucs", bien que je ne le comprenne pas complètement, donc je ne suis pas en mesure de déterminer la raison exacte avec certitude.

Voici le code pour vérifier cela (basé sur l' extrait de

Initialisation:

import matplotlib
import matplotlib.pyplot as plt
import matplotlib.ticker

%matplotlib inline

matplotlib.rcParams['figure.figsize'] = (24, 6)
matplotlib.rcParams['figure.dpi'] = 150

from resource import getrusage
from resource import RUSAGE_SELF

def friendlyPlot():
    fig, ax = plt.subplots()
    ax.plot(range(1000))
    fig.savefig('tmp.png')
    plt.close('all')

Test réel:

print("before any:  {:7d} kB".format(getrusage(RUSAGE_SELF).ru_maxrss))
friendlyPlot()
print("before loop: {:7d} kB".format(getrusage(RUSAGE_SELF).ru_maxrss))
for i in range(50):
    friendlyPlot()
print("after loop:  {:7d} kB".format(getrusage(RUSAGE_SELF).ru_maxrss))
import gc ; gc.collect(2)
print("after gc:    {:7d} kB".format(getrusage(RUSAGE_SELF).ru_maxrss))

En l'exécutant pendant 50 itérations de la boucle, j'obtiens:

before any:    87708 kB
before loop:  106772 kB
after loop:   786668 kB
after gc:     786668 kB

En l'exécutant pendant 200 itérations de la boucle, j'obtiens:

before any:    87708 kB
before loop:  100492 kB
after loop:  2824316 kB
after gc:    2824540 kB

qui montre l'augmentation presque linéaire de la mémoire avec les itérations.

Passons maintenant au correctif / solution de contournement: appelez matplotlib.interactive(False) avant le test-snippet, puis exécutez-le.

Avec 50 itérations:

before any:    87048 kB
before loop:  104992 kB
after loop:   241604 kB
after gc:     241604 kB

Et avec 200 itérations:

before any:    87536 kB
before loop:  103104 kB
after loop:   239276 kB
after gc:     239276 kB

Ce qui confirme qu'il ne reste qu'une augmentation constante (indépendante des itérations).

En utilisant ces chiffres, je fais une estimation approximative de la taille de la fuite par itération:

(786668-(241604 - 104992))/50   = 13001.12
(2824316-(241604 - 104992))/200 = 13438.52

Et pour une seule itération de la boucle, j'obtiens 13560 . Ainsi, la quantité de fuite par itération est nettement inférieure à la taille de l'image, qu'elle soit brute (> 3 Mo) ou compressée au format png (54 Ko).

Aussi, étrangement, exécuter un test à petite échelle (seulement quelques itérations) à plusieurs reprises dans la même cellule sans redémarrer le noyau est beaucoup moins cohérent, je n'ai pas été en mesure de comprendre cela ou de déterminer un modèle.

J'espère que quelqu'un avec plus de connaissances sur les éléments internes pourra le prendre à partir d'ici, car je manque de temps et de connaissances pour approfondir cela maintenant.

Ça marche

Cette page vous a été utile?
0 / 5 - 0 notes