Skip to content

[Bug]: memory baking figure is not freed when figure is closed #29743

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
dnicolodi opened this issue Mar 12, 2025 · 10 comments
Open

[Bug]: memory baking figure is not freed when figure is closed #29743

dnicolodi opened this issue Mar 12, 2025 · 10 comments

Comments

@dnicolodi
Copy link
Contributor

dnicolodi commented Mar 12, 2025

Bug summary

It seems that a reference to the figure object is kept in some global state. Executing the code below and closing the figures that pop up results in a linear increase in used memory. This happens with Matplotlib 3.9.1 and later. The latest release that does not exhibit the problem is Matplotlib 3.8.4.

Adding fig.clf() after the plt.show() in the code below (clearing the figure after it has been displayed and closed by the user) results in the memory leak being significantly reduced, but not eliminated.

Code for reproduction

import gc
import psutil
import numpy as np
import matplotlib.pyplot as plt

p = psutil.Process()

d = np.linspace(0, 1, 1_000_000)

for i in range(10):
    fig, ax = plt.subplots()
    ax.plot(d)
    plt.show()
    gc.collect()
    print(p.memory_info().rss)

Actual outcome

185729024
254550016
321282048
389459968
456253440
524554240
592330752
659394560
727457792
794468352

Expected outcome

183324672
251961344
253427712
253464576
253493248
254521344
254529536
253464576
253468672
254476288

Additional information

The leak has been introduced sometime between version 3.8.4 (which gives the expected outcome output above) and version 3.9.1 (which gives the actual outcome output above). It reproduces with Matplotlib version 3.9.4 and 3.10.1

Operating system

Windows

Matplotlib Version

3.9.1

Matplotlib Backend

qtagg

Python version

3.12.9

Jupyter version

No response

Installation

conda

@timhoffm
Copy link
Member

The pyplot figure manager still holds a reference to the figure. Use plt.close(fig) to remove that reference.

@dnicolodi
Copy link
Contributor Author

Adding plt.close() does not make any difference.

@dnicolodi
Copy link
Contributor Author

The issue is reproducible with the qtagg backend but not with the tkagg backend. I'm using PyQt version 5.15.9

@dnicolodi
Copy link
Contributor Author

This is funny: this code

import gc
import psutil
import numpy as np
import matplotlib.pyplot as plt

p = psutil.Process()

d = np.linspace(0, 1, 1_000_000)

for i in range(20):
    fig, ax = plt.subplots()
    sys.getrefcount(fig)
    ax.plot(d)
    plt.show(block=False)
    plt.close(fig)
    gc.collect()
    print(p.memory_info().rss)

reproduces the issue also on Matplotlib 3.8.4. It seems that running the GUI mainloop makes a difference.

@timhoffm
Copy link
Member

With the qtagg backend I get on main

Image

@dnicolodi
Copy link
Contributor Author

Which reproducer did you ran to get that? Which version of Qt? Which Python binding? Can you please try to reproduce with a released version so we can determine if the issue is specific to my system? Unfortunately I don't have have a setup that easily allows me to compile Matplotlib from source.

@timhoffm
Copy link
Member

Ok, the above was on my linux dev machine. I can reproduce a linear increase both on linux and windows in a fresh conda environment (conda create -n mplleak -c conda-forge matplotlib python=3.12 psutil). I also see this on both qtagg and tkagg backends, so it's likely not a backend-specific issue.

Needs further investigation, but we have a starting point now.

@tacaswell
Copy link
Member

You have to run the GUI event loop to allow the c++ objects to be cleaned up.

@timhoffm
Copy link
Member

timhoffm commented Mar 13, 2025

You have to run the GUI event loop to allow the c++ objects to be cleaned up.

I've tried using Application.instance().processEvents() (with qtagg backend), but the memory increases.

import gc
import psutil
import numpy as np
import matplotlib.pyplot as plt
from PySide6.QtWidgets import QApplication

p = psutil.Process()

rss = []

for i in range(20):
    fig, ax = plt.subplots()
    plt.show(block=False)
    plt.close(fig)
    Application.instance().processEvents()
    gc.collect()
    rss.append(p.memory_info().rss)

plt.figure()
plt.plot(rss, 'o-')
plt.show()

@tacaswell
Copy link
Member

I'm also seeing run-away memory usage, I think we need to bisect this back to sort out when this started to happen.

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

No branches or pull requests

3 participants