Memory increases with matplotlib and tkinter

别来无恙 提交于 2021-02-10 18:49:09

问题


Problem:

I have a tkinter window with a matplotlib graph showing some lines (part of a much larger program). Periodically I add a new line, deleting an oldest one to limit the number of lines shown. Unfortunately the program grows in size continuously. I've seen this on both Windows and Linux.

Question:

How should I write a long-running graph frame to show the last n lines which doesn't use up all my memory? Preferably by tweaking the code below, but I'm prepared for a complete re-write if required.

I've stripped the offending parts and been doing some testing.

The crucial call appears to be canvas.draw(). Even if no lines are added to the figure the memory used gradually increases with time. Without this call the memory does not increase. The memory use increases faster when more lines are present in the plot. If the axes are not created (i.e. no fig.add_subplot(1, 1, 1)) then there is no increase in memory.

Deleting lines and reclaiming memory from matplotlib has been discussed before (How to remove lines in a Matplotlib plot). A weakref to the line being deleted confirms that it is being removed, and as the issue stil exists even when no lines are plotted I suspect that this is not the root cause.

The closest similar problem seems to be Declaration of FigureCanvasTkAgg causes memory leak, where calls to the axes for plotting (rather than to pyplot) solved the issue. This does not seem to apply in this case - I'm not using pyplot for my plotting, I still see the memory increase if I plot directly on the axes (ax.plot(x, y, '.-')), and again, I still see the issue when there are no lines plotted.

Memory usage was monitored using mprof, and I've also watched it happen using system diagnostics on Windows7. An example of the memory usage as seen by mprof: mprof plot of memory usage increasing

Here's the code I've presently got. This has been much cut down from the initial use case (_refresh_plot would not normally do anything unless new data had been added to a queue from another thread, etc), but still shows the issue.

import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib as mpl
import weakref

class PlotFrame:
    def __init__(self, parent):
        self.fig = mpl.figure.Figure()
        self.canvas = FigureCanvasTkAgg(self.fig, master=parent)
        self.canvas.get_tk_widget().pack()
        self.ax = self.fig.add_subplot(1, 1, 1)
        self.ax.set_ylim((0, 150))
        self.ax.set_xlim((0, 12))

        self.counter = 0

        timer = self.fig.canvas.new_timer(interval=20)
        timer.add_callback(self._refresh_plot, ())
        timer.start()

    def _refresh_plot(self, arg):
        if True:  # True to plot 1 or more lines, False for no lines at all
            if len(self.ax.lines) > 2:  # Remove the oldest line
                existing_lines = self.ax.get_lines()
                # r = weakref.ref(existing_lines[0])  # weakref to check that the line is being correctly removed
                old_line = existing_lines.pop(0)
                old_line.remove()
                del old_line, existing_lines
                # print('Old line dead? {}'.format(r() is None))  # Prints Old line dead? True

            # Define a line to plot
            x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
            y = [(v - abs(len(x) - self.counter % (2 * len(x)))) ** 2 for v in x]
            self.counter += 1

            new_line = mpl.lines.Line2D(x, y, linestyle='-', marker='.')
            self.ax.add_line(new_line)
            del new_line, x, y

        self.canvas.draw()

if __name__ == "__main__":
    root = tk.Tk()
    cpp = PlotFrame(root)
    root.mainloop()

Apologies for the long post. I've been staring at this for so long now that it may well be a silly error on my part.

来源:https://stackoverflow.com/questions/53656351/memory-increases-with-matplotlib-and-tkinter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!