随着内存泄漏,使用 Matplotlib 和 Python 运行 在循环中绘图越来越慢

Plotting in a loop using Matplotlib and Python running slower and slower over time with memory leaking

我正在尝试使用动态图表显示实时电压数据,动态图表显示记录的电压随时间的变化,并在具有其他小部件的 Tkinter window 中显示绘图。该程序还必须根据测量值通过开关和继电器采取各种动作,并通过短信和电子邮件发送这些动作的通知。在对不同方法进行多次迭代之后,我选择了 Matplotlib,在每个循环中使用 clear() 和 draw() 来更新绘图,并将电子邮件发送到子进程中,这样互联网延迟就不会停止电压采样很长一段时间。它在 Raspberry Pi 4 上以大约 1/4 秒的绘图更新速率绘制 3 条 500 点的轨迹,效果很好。

然而,当我让程序运行时,我发现循环时间越来越长,在16小时后循环时间从1/4秒减慢到2.5秒。此外,虚拟内存大小已从 105MB 增加到 500MB。

我添加了代码来隔离罪魁祸首并将其缩小到对 Matplotlib 的 clear() 调用。这是一个图表,显示了在 while 循环中 运行ning 程序在 3 小时内循环的每个组件所花费的时间。

Time components of each loop over time

在这里您可以看到,在循环 运行ning 的 3 小时内,调用 clear()(红线)所花费的时间从 0.055 秒增加到 0.7 秒。循环的所有其他组件几乎保持不变,除了对 plot() 的调用有几个大峰值。但是 clear() 调用所花费的时间不断增加,这是不可接受的。很有可能也是内存泄漏的罪魁祸首。

我在以下程序中提取了与此问题相关的程序部分。这可以在python3中提取并运行。它测量在 while 循环的每个循环中调用 clear()、plot() 和 draw() 所花费的时间,并将其动态地绘制在屏幕上。你可以看到对 clear 的调用在慢慢增加。该程序还允许您查看系统中其他活动对执行这些调用的时间的影响。可以看到只要移动鼠标就有效果了。尝试打开浏览器或播放视频。

import time
from time import sleep
from datetime import datetime
import math                      # provides math functions
from tkinter import *               # provides GUI capability
from tkinter import ttk
from tkinter import messagebox
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

# Set root window to center of screen. Parameters are size of window itself.
def center_window(self, width=300, height=200):
    # get screen width and height
    screen_width = self.winfo_screenwidth()
    screen_height = self.winfo_screenheight()

    # calculate position x and y coordinates
    x = (screen_width/2) - (width/2)
    y = (screen_height/2) - (height/2)
    self.geometry('%dx%d+%d+%d' % (width, height, x, y))

def alldone(*args):
    global running
    running = False

root = Tk()                         # Create base window class as root
root.wm_title("Green Canyon APFM System Ver: 0.6")
center_window(root,1024,580)
running = True
root.protocol("WM_DELETE_WINDOW", alldone)       # Define routine to call if window closes

time_plot = []
clear_plot = []
plot_plot = []
draw_plot = []

figure3 = plt.Figure(figsize=(9.9,5.8), dpi=100)
ax3 = figure3.add_subplot(111)
ax3.plot(time_plot,clear_plot,"r-", label="clear() call")    # Red line
ax3.plot(time_plot,plot_plot,"g-", label="plot() calls")     # Green line
ax3.plot(time_plot,draw_plot,"b-", label="clear() call")     # Blue line
scatter3 = FigureCanvasTkAgg(figure3, root) 
scatter3.get_tk_widget().grid(column=0, row=0, rowspan=2)
ax3.legend(loc=6)
ax3.set_xlabel('Time (secs)')
ax3.set_ylabel('Task time (sec) for the calls')
ax3.set_title(' EndTime = '+datetime.now().strftime("%H:%M:%S"))
ax3.grid()
scatter3.draw()
loopclock = time.time()
pclock = 0.0

"""-------------------------------------------------------------------------------
   Main Loop
-------------------------------------------------------------------------------"""
t2=t3=t4=t5=t6=t7=t8=0.0
t2a=t3a=t4a=t5a=t6a=t7a=t8a=0.0
nn = 0
while running:
    c2 = time.time()
    """----------------------------------------------------------------------
    This segment update the plot on the screen
    ----------------------------------------------------------------------"""
    ax3.clear()
    c3 = time.time()
    t2 = c3 - c2
    ax3.plot(time_plot,clear_plot,"r-", label="clear() call")    # Red line
    ax3.plot(time_plot,plot_plot,"g-", label="plot() calls")    # Green line
    ax3.plot(time_plot,draw_plot,"b-", label="draw() call")     # Blue line
    c4 = time.time()
    t3 = c4 - c3

    ax3.legend(loc=6)
    c5 = time.time()
    t4 = c5 - c4
    ax3.set_xlabel('Time (secs)')
    ax3.set_ylabel('Voltage (V)')
    c6 = time.time()
    t5 = c6 - c5
    looptime = time.time() - loopclock
    loopclock = time.time()
    ax3.set_title('          EndTime = '+datetime.now().strftime("%H:%M:%S")+
                  "  LT="+f"{looptime:.2f}"+
                  f"\n  {nn:4d}|{t2:.3f}|{t3:.3f}|{t4:.3f}|{t5:.3f}|{t6:.3f}|{t7:.3f}|{t8:.3f}")
    ax3.grid()
    c7 = time.time()
    t6 = c7 - c6
    scatter3.draw()
    c8 = time.time()
    t7 = c8 - c7

    root.update()
    c9 = time.time()
    t8 = c9 - c8

    # print out the max values in every 15 second intervals for plotting
    t2a = max(t2,t2a)
    t3a = max(t3,t3a)
    t4a = max(t4,t4a)
    t5a = max(t5,t5a)
    t6a = max(t6,t6a)
    t7a = max(t7,t7a)
    t8a = max(t8,t8a)
    nn += 1
    if time.time() > pclock + 15.0:
        pclock = time.time() 
        print(f"{nn:5d},{t2a:.4f}, {t3a:.4f}, {t4a:.4f}, {t5a:.4f}, {t6a:.4f}, {t7a:.4f}, {t8a:.4f}")
        t2a=t2
        t3a=t3
        t4a=t4
        t5a=t5
        t6a=t6
        t7a=t7
        t8a=t8

    xtime = (time.time() + 2209132800) % 60.0
    if len(time_plot) >= 500:
        time_plot.pop(0)
        clear_plot.pop(0)
        plot_plot.pop(0)
        draw_plot.pop(0)
    if len(time_plot) > 0 and time_plot[-1] > xtime:          # If we are rolling over to the next minute, decrease all the old values by 1 minute
        for j in range(len(time_plot)):
            time_plot[j] -= 60.0

    time_plot.append(xtime)
    clear_plot.append(t2)
    plot_plot.append(t4)
    draw_plot.append(t7)

root.quit()
sys.exit()

非常感谢任何关于如何修复此 CPU 和内存泄漏的指示。我找到了关于在单独的进程中进行绘图的建议,以便在任务终止时检索所有内存。但是终止每个循环绘制到屏幕上的任务可能会使显示空白或使其闪烁。想知道是否有更好的方法使用 python 和 matplotlib 进行动态绘图。我正在使用 python 3.7 和 Matplotlib 3.0.2.

问题已解决。我安装了 Matpoltlib 3.1.4,问题消失了。