Ipython: Vazamento de memória com% matplotlib embutido

Criado em 19 dez. 2014  ·  23Comentários  ·  Fonte: ipython/ipython

Oi pessoal

Eu encontrei um problema. Basta iniciar o código e olhar a memória. Em seguida, exclua "% matplotlib inline" e inicie novamente.

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

Comentários muito úteis

Vou concordar que uma correção para este problema seria apreciada.

Todos 23 comentários

Também encontrei esse bug. Existe alguma maneira de obter plotagens embutidas sem vazamentos de memória? Não quero lançar processos separados para cada parcela, uma vez que as matrizes são muito grandes.

Você pode verificar isso quando o uso de memória aumentar:

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

Essa é uma lista onde as figuras estão sendo armazenadas. Eles deveriam estar lá apenas temporariamente, mas talvez estejam crescendo sem serem liberados.

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

BTW, estou plotando usando o método .plot() em dataframes do pandas.

OK, chega dessa teoria.

É possível que o pandas também mantenha alguns dados em torno dos gráficos internamente. O relatório original não envolve pandas, no entanto.

Quanta memória cada plotagem adicional parece adicionar?

ok, este parece ser o meu caso, eu estava usando o pandas 0.16.0, mas o problema foi corrigido no master:

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

Ótimo, obrigado. Deixando em aberto já que o relatório original não envolvia pandas.

Isso pode ser reproduzido de forma mais simples:

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()

Isso não vaza memória, então é algo do lado do IPython, não do lado do pyplot (eu acho).

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 Com o seu código de teste, o IPython no Windows 7 consome aproximadamente

@asteppke O primeiro ou segundo bloco?

@tacaswell Com seu primeiro código de teste ( %matplotlib inline ) o consumo de memória sobe para 1,7 GB. Em contraste, ao usar a segunda peça ( matplotlib.use('agg') ), o uso de memória oscila apenas entre 50 MB e 100 MB.

Ambos os testes são executados com Python 3.4 e notebook IPython versão 4.0.5.

Eu brinquei com isso um pouco mais. Percebo que se eu executar novamente o loop for no exemplo de @tacaswell algumas vezes, o uso de memória não aumenta - parece ser o número que você cria em uma única célula que importa. O IPython certamente mantém uma lista de todas as figuras geradas na célula para o backend embutido, mas essa lista está definitivamente sendo limpa após a execução da célula, o que não diminui o uso de memória, mesmo depois de gc.collect() .

Nosso código pode estar interagindo mal com algo no matplotlib? Achei que _pylab_helpers.Gcf parecia provável, mas não parece estar se segurando em nada.

Tentei pegar uma referência a uma das figuras e chamar gc.get_referrers() nela; além da referência que tive em user_ns, todos os outros pareciam objetos mpl - presumivelmente muitos deles estão em loops de referência. A que objeto é mais provável que outra coisa estaria inadequadamente mantendo uma referência?

Vou colocar isso na 'lista de desejos' do marco. Queremos consertá-lo, mas no momento não temos certeza de como progredir na identificação do bug e não acho que valha a pena esperar versões para isso.

Qualquer pessoa que puder progredir ganha pontos de brownie Bolo também.

Não é realmente um progresso, mas a memória parece estar perdida em algum lugar dentro do kernel. Nem chamar gc.collect() após ou dentro do loop ajuda, e summary.print_(summary.summarize(muppy.get_objects())) não encontra nenhuma memória vazada. Nem a configuração de _N e _iN para None ajuda. É realmente misterioso.

Eu também me perguntei se ele estava criando objetos não coletáveis, mas aqueles deveriam terminar em gc.garbage quando não há outras referências a eles, e isso ainda está vazio quando vejo que está usando muita RAM.

Acho que alguém que conhece essas coisas vai ter que usar ferramentas de nível C para rastrear qual memória não está sendo liberada. Não há evidências de objetos Python extras sendo mantidos vivos em qualquer lugar que possamos encontrar.

Vou concordar que uma correção para este problema seria apreciada.

Nós sabemos, mas no momento ninguém descobriu a causa do bug.

+1

+1

BTW, ainda estou tendo esse problema de vez em quando no último matplotlib, pandas, jupyter, ipython. Se alguém souber de algum depurador que possa ajudar a solucionar esse problema de comunicação de vários processos, entre em contato.

Talvez isso tenha algo a ver com o mecanismo de cache do navegador?

Boa ideia, mas acho que não. É o processo do IPython ocupando memória extra, não o navegador, e
A reprodução de @tacaswell não envolve o envio de plotagens ao navegador.

Olá, acredito que encontrei parte do culpado e uma maneira de reduzir significativamente, mas não completamente, esse problema!

Depois de rolar pelo código ipykernel/pylab/backend_inline.py , percebi que o modo interativo armazena muito "tramas", embora não o entenda completamente, então não sou capaz de identificar o motivo exato com certeza.

Aqui está o código para verificar isso (com base no snippet de @tacaswell acima), útil para qualquer pessoa que esteja tentando implementar uma correção.

Inicialização:

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')

Teste real:

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))

Executando-o por 50 iterações do loop, obtenho:

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

Executando-o por 200 iterações do loop, obtenho:

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

que mostra o aumento quase linear na memória com iterações.

Agora, para a correção / solução alternativa: chame matplotlib.interactive(False) antes do snippet de teste e, em seguida, execute-o.

Com 50 iterações:

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

E com 200 iterações:

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

O que confirma que resta apenas um aumento constante (independente das iterações).

Usando esses números, faço uma estimativa aproximada do tamanho do vazamento por iteração:

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

E para uma única iteração do loop, obtenho 13560 . Portanto, a quantidade de vazamento por iteração é significativamente menor do que o tamanho da imagem, seja ela bruta (> 3 MB) ou compactada por PNG (54 KB).

Além disso, estranhamente, executar um teste em pequena escala (apenas algumas iterações) repetidamente na mesma célula sem reiniciar o kernel é muito menos consistente, não fui capaz de entender isso ou determinar um padrão.

Espero que alguém com mais conhecimento do interior possa continuar a partir daqui, já que não tenho tempo e conhecimento para mergulhar mais fundo nisso agora.

funciona

Esta página foi útil?
0 / 5 - 0 avaliações