PyPlot doesn't release mem after close()

I’m creating and saving a series of images using PyPlot and savefig. However, despite using ioff() and closing all the figures, the memory increases linearly with number of figures that are created, saved and closed. I upgraded to the latest julia 1.5.2 and pkg> up and that did not solve the problem. Does anyone experience the same problem or preferably have a solution or workaround? Below is a MWE:

using PyPlot
ioff()

for i = 1:2000
    println(i)
    figure()
    plot(randn(30000))
    savefig("figure_$i.png")
    close()
end

You are creating a new figure each time with figure(). Try removing that?

I thought close() did that, i.e. closed the most recently opened figure. Even if I do figure(1) and close(1) or close("all") I experience the same issue unfortunately.

What happens if you just remove the figure() call altogether?

You are creating a new array of random numbers at each step. Why wouldn’t you expect that to allocate memory?

Yes but I thought that would release with close(). I tried use an existing array created outside the for loop but still the same problem. There seems to be some other reason, if I remove both the plot and the savefig part it still increases memory linearly, but with a slower rate. Somehow the memory is not released with close() or plt.cla() or plt.clf().

That has nothing to do with this.

It does seem like the julia version is retaining more memory than the pure python version. Without digging much deeper I suspect PyCall isn’t handling cross-language circular reference well.

3 Likes

Yes, you are correct, the equivalent python version of the code (below) does release the memory

from pylab import *
ioff()
for i in range(1,2001):
  figure()
  plot(randn(30000))
  savefig("figure_"+str(i)+".png")
  close()

Would it be possible to call the python garbage collector to clear the memory? I have some rather large plots and have to close Julia to free up memory between plot sets so I don’t get an error.

Maybe this is helpful from PyCall’s docs:

  • If a new reference is returned by a Python function, immediately convert the PyPtr return values to PythonObject objects in order to have their Python reference counts decremented when the object is garbage collected in Julia. i.e. PythonObject(ccall(func, PyPtr, ...)) . Important : for Python routines that return a borrowed reference, you should instead do pyincref(PyObject(...)) to obtain a new reference.
  • You can call pyincref(o::PyObject) and pydecref(o::PyObject) to manually increment/decrement the reference count. This is sometimes needed when low-level functions steal a reference or return a borrowed one.

I found a workaround: if I add a GC.gc() inside the loop the memory is released. However, it has no effect (i.e. the memory is not released) if I add it outside and after the loop. Also, note that GC.gc() can be added anywhere in the loop and memory is released (perhaps something to do with the local scope).

using PyPlot
ioff()

for i = 1:2000
    println(i)
    figure()
    plot(randn(30000))
    savefig("figure_$i.png")
    close()
    GC.gc()
end
1 Like