Skip to content

Memory leak in plt.close() when unshown figures in GUI backends are closed #20300

Closed

Description

@P-Takenaka

Bug report

Bug summary

When creating multiple plots (e.g. in a loop), the allocated memory for the process increases in each iteration even though the figure is cleared each time.

Code for reproduction

import gc
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(111)
for i in range(5000):
    fig.clf()
    plt.cla()
    ax.plot([0,1,2,3,4,5], [0,1,2,3,4,5])
    plt.close('all')
    plt.close(fig)

    gc.collect()

Expected outcome

The allocated memory should not increase in each iteration.

Matplotlib version

  • Operating system: Arch Linux
  • Matplotlib version (import matplotlib; print(matplotlib.__version__)): 3.4.2
  • Matplotlib backend (print(matplotlib.get_backend())): TkAgg
  • Python version: 3.9.5
  • Jupyter version (if applicable):
  • Other libraries:

Matplotlib was installed via pip.

Activity

  1. timhoffm commented on May 25, 2021

    @timhoffm
    Member

    This indeed slowly increases memory usage (checked with memory_profiler and 500 samples instead of 5000):

    grafik

    However, the code is not reasonable in itself. It seems you are trying wildly delete everything. But though that, fig.clf() cleans the figure, including it's axes, plt.cla() does nothing then, and the ax object continues to live detached from any figure so that the data from ax.plot will not show up in any figure.

    While that doesn't explain the memory leak, let's try and start from a realistic example: What are you trying to do exactly? Do you only want to replace data inside the Axes? If so, the only cleanup command necessary should be ax.clear() (or historically synonym ax.cla()). If you need to wipe the whole figure, fig.clf() is the way to go (But I expect fig.close(); fig = plt.figure() should be similarly efficient).

  2. P-Takenaka commented on May 25, 2021

    @P-Takenaka
    Author

    I am aware that the example code contains redundant clear commands, I wanted to emphasize that I tried all the commands that solved past issues of a similar kind. However, after your remarks regarding a floating axis object I noticed that this might be partially the reason for the issue, and only the following combination of clear commands in each iteration is already enough to result in the memory growth problem:

    plt.cla()
    plt.close()

    In my use case I only need to clear the axis data, therefore for me the issue would be solved by only using plt.cla() as you said. I unnecessarily went the way to rebuild the whole figure in each iteration at first, resulting in me using the above functions. Though I am still wondering why the code results in the memory leak. Is there no way to completely clean up a figure short of stopping the whole python process? Unfortunately I am not familiar enough with the inner workings of matplotlib.

  3. timhoffm commented on May 25, 2021

    @timhoffm
    Member

    Regular usage with clearing the Axes or figure does not have a memory leak. It seems that plt.close() doesn't completely get rid of the data.

    Axes clearing

    fig = plt.figure()
    ax = fig.add_subplot(111)
    for i in range(500):
        ax.clear()
        ax.plot([0,1,2,3,4,5], [0,1,2,3,4,5])
    

    grafik

    Figure clearing

    fig = plt.figure()
    for i in range(500):
        fig.clf()
        ax = fig.add_subplot(111)
        ax.plot([0,1,2,3,4,5], [0,1,2,3,4,5])
    

    grafik

    Figure closing

    Simple figure closing

    for i in range(500):
        fig = plt.figure()
        ax = fig.add_subplot(111)
        ax.plot([0,1,2,3,4,5], [0,1,2,3,4,5])
        plt.close(fig)
    

    grafik

    Closing with garbage collection

    Strips of some memory, but doesn't help with the leak. All this does is that the gc slows down execution:

    for i in range(500):
        fig = plt.figure()
        ax = fig.add_subplot(111)
        ax.plot([0,1,2,3,4,5], [0,1,2,3,4,5])
        plt.close(fig)
        gc.collect()
    

    grafik

  4. jklymak commented on May 25, 2021

    @jklymak
    Member

    I'm relatively sure tK leaking has been documented before. Does this happen for other backends?

  5. timhoffm commented on May 25, 2021

    @timhoffm
    Member

    As proposed in #5795, this has to do with created but not-shown GUI windows (all tests done with Qt on linux).

    It works if you show the windows and close them afterwards

    for i in range(500):
        fig = plt.figure()
        ax = fig.add_subplot(111)
        ax.plot([0,1,2,3,4,5], [0,1,2,3,4,5])
        plt.show(block=False)
        plt.pause(0.2)
        plt.close(fig)
    

    grafik

    The edges are probably due to garbage collection.

    It works if you use a non-GUI backend

    import matplotlib as mpl
    mpl.use('agg')
    import matplotlib.pyplot as plt
    
    for i in range(500):
        fig = plt.figure()
        ax = fig.add_subplot(111)
        ax.plot([0,1,2,3,4,5], [0,1,2,3,4,5])
        plt.savefig(f'test{i:03d}.png')
        plt.close(fig)
    

    grafik

    Summary

    This only happens if you work in a GUI backend, create new figures, but don't show them. This way of usage is not reasonable. There are working usage patterns for all relevant scenarios.

    I'm closing this as "won't fix" because the memory leak doesn't happen for proper usage, and I assume that debugging the GUI framework memory management would be quite hard.

  6. changed the title [-]Memory not freed up correctly[/-] [+]Memory leak in plt.close() if unshown figures in GUI backends are closed[/+] on May 25, 2021
  7. changed the title [-]Memory leak in plt.close() if unshown figures in GUI backends are closed[/-] [+]Memory leak in plt.close() when unshown figures in GUI backends are closed[/+] on May 25, 2021
  8. tacaswell commented on May 27, 2021

    @tacaswell
    Member

    The edges are probably due to garbage collection.

    gc + circular references is the source of the jagged edges, Python only does the more expensive sweeps every so often and we have a lot of circular references!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions