I have a Python program that gets data from a measurement instrument and plots the data using matplotlib. A separate thread is used to trigger updating of the plots at fixed time intervals.
After a while, the program will take up huge amounts of memory (gigabytes after a few hours). If I put some pressure on the system by running another application that will consume a lot of memory, my Python program will at some point start to release the excessive memory used up by matplotlib (ending up at about 60 MB or so). Releasing the memory does not seem to have any negative effects on the operation of my program. This tells me that the large junk of memory used by matplotlib is not vital (if not useless) in my application.
The simplified code below is a self-contained example that illustrates the effect. The issue does not happen if the code is modified to skip the axes.legend(…) call, i.e., if no legend is drawn. You can run the code with / without the legend by setting
False on line 111.
I believe the unnecessary memory usage related to the legend is a bug, or at least something very obscure that seems wrong to me. How can this issue be avoided or fixed?
import wx import matplotlib import matplotlib.pyplot as plt from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas import time from numpy.random import rand from threading import Thread from setproctitle import setproctitle print ( 'Matplotlib version: ' + matplotlib.__version__ ) class measurement_instrument(): # measurement instrument def __init__(self, num_val): self.val1 = None self.val2 = None self._num_val = num_val def read(self): # get new measurement data: u = rand(1) if u < 0.3: self.val1 = rand(self._num_val) self.val2 = None elif u < 0.7: self.val1 = None self.val2 = rand(self._num_val) else: self.val1 = rand(self._num_val) self.val2 = rand(self._num_val) class measurement_thread(Thread): # background thread that takes measurements at fixed time intervals def __init__(self, instrument): Thread.__init__(self) self._instrument = instrument self.start() def run(self): while True: self._instrument.read() time.sleep(0.01) class plots_frame(wx.Frame): def __init__(self, instrument, use_legend): # Access to measurement instrument (to get data for plotting): self._instrument = instrument # Create the window: wx.Frame.__init__(self, parent=None, title='Instrument Data', style=wx.DEFAULT_FRAME_STYLE) # Create a wx panel to hold the plots p = wx.Panel(self) p.SetBackgroundColour(wx.NullColour) # Set up figure: self.figure = plt.figure() self.axes = self.figure.add_subplot(1,1,1) self.axes.grid() self.canvas = FigureCanvas(p, -1, self.figure) self._do_legend = use_legend # set this to False for no legend in the plot # wx sizer / widget arrangement within the window: sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.canvas, 1, wx.EXPAND) p.SetSizer(sizer) # apply the sizer to the panel sizer.SetSizeHints(self) # inform frame about size hints for the panel (min. plot canvas size and stuff) # install wx.Timer to update the data plot: self.Bind(wx.EVT_TIMER, self.on_plot_data) # bind to TIMER event self.plot_timer = wx.Timer(self) # timer to trigger plots wx.CallAfter(self.plot_timer.Start,100) # Show the wx.Frame self.Show() def on_plot_data(self,event): # Remove the old lines that are currently in the plot: while len(self.axes.lines) > 0: self.axes.lines.remove() # Plot the new instrument data: if self._instrument.val1 is not None: self.axes.plot(self._instrument.val1, label='val-1', color='r') if self._instrument.val2 is not None: self.axes.plot(self._instrument.val2, label='val-2', color='b') # Legend (commenting out this line will avoid the memory issue!): if self._do_legend: leg = self.axes.get_legend() # get the old legend, if there is one if leg is not None: leg.remove() # delete the old legend, if there is one self.axes.legend(loc=1) # show new legend # Refresh the plot window: self.canvas.draw() self.Refresh() ########## main: use_legend = True # use this to turn the legend on / off if use_legend: setproctitle('memorytest_with_legend') else: setproctitle('memorytest_without_legend') app = wx.App() instrument = measurement_instrument(num_val=300) dataplots = plots_frame(instrument, use_legend) measurements = measurement_thread(instrument) app.MainLoop()