I'm seeing a memory leak with calls to subplot.clear() and canvas.draw() on MacOS. The same code shows no leakage on unix.
Here is a simple script that demonstrates the problem.
#!/usr/bin/env python
from __future__ import division
"""Demonstrate a memory leak in matplotlib on MacOS with TkAgg
Variants:
- Comment out subplot.clear(): this reduces the leak by about 4/5
- Comment out canvas.draw(): this reduces the leak by about 1/5
- Comment out both: there should be essentially no leakage
The leak rate seems to be affected by the update rate
(larger update interval causes lower leakage),
which suggests it's not a simple "x amount of leakage per call".
"""
import resource
import time
import Tkinter
import matplotlib
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class MemoryLeaker(Tkinter.Frame):
"""Demonstrate a memory leak in matplotlib
"""
def __init__(self, master, updateInterval=0.1, updatesPerReport=25):
"""Construct a MemoryLeaker
Inputs:
- master: master widget
- updateInterval: interval at which subplot.clear and canvas.draw are called
- updatesPerReport: number of updates between memory reports
"""
Tkinter.Frame.__init__(self, master)
self._updateInterval = float(updateInterval)
self._updatesPerReport = int(updatesPerReport)
self.figure = matplotlib.figure.Figure(figsize=(8, 2), frameon=True)
self.canvas = FigureCanvasTkAgg(self.figure, self)
self.canvas.get_tk_widget().grid(row=0, column=0, sticky="news")
self.subplot = self.figure.add_subplot(1, 1, 1)
print "time max RSS leak rate"
print "(sec) (kb) (kb/sec)"
self._prevTime = time.time()
self._prevMem = float("nan")
self._reportCount = 0
self.updatePlot()
def updatePlot(self):
"""Update the plot; calls itself at the update interval
"""
if self._reportCount == 0:
self.reportMem()
self._reportCount = (self._reportCount + 1) % self._updatesPerReport
self.subplot.clear()
self.canvas.draw()
self.after(int(self._updateInterval * 1000), self.updatePlot)
def reportMem(self):
currTime = time.time()
dTime = currTime - self._prevTime
res = resource.getrusage(resource.RUSAGE_SELF)
currMem = res.ru_maxrss / 1024 # maximum resident set size utilized (in kb)
leakRate = (currMem - self._prevMem) / dTime
self._prevTime = currTime
self._prevMem = currMem
print "%6.1f %6d %6.1f" % (dTime, currMem, leakRate)
if __name__ == "__main__":
root = Tkinter.Tk()
wdg = MemoryLeaker(root, updateInterval=0.1, updatesPerReport=25)
wdg.pack(expand=True, fill="both")
root.mainloop()
P.S. my current setup is:
- MacOS 10.9
- python.org python 2.7.8
- matploblib 1.3.1
- a pre-release of Tcl/Tk 8.5.17
but I've seen this on many earlier versions, as well
I have not tried it with matplotlib 1.4.1 yet (due to problems packaging that with py2app).