我正在尝试使用动态图显示实时电压数据,该图显示记录的电压随时间的变化,并在具有其他小部件的Tkinter窗口中显示绘图。该程序还必须根据测量值对开关和继电器采取各种操作,并通过短信和电子邮件发送这些操作的通知。经过对不同方法的多次迭代,我决定在每个循环中使用clear((和draw((来更新图,并将电子邮件发送到子进程中,这样互联网延迟就不会长时间停止电压采样。它在 Raspberry Pi 上以大约 1/4 秒的绘图更新速率绘制 3 条 500 点的迹线,每条迹线 4 个非常有效。
但是,当我让程序运行时,我发现循环时间越来越长,循环时间从 1/4 秒减慢到 16 小时后的 2.5 秒。此外,虚拟内存大小已从 105MB 增加到 500MB。
我添加了代码来隔离罪魁祸首,并将其缩小到对 Matplotlib 的 clear(( 调用。下图显示了循环的每个组件在 while 循环中运行的程序在 3 小时内所花费的时间。
每个循环随时间变化的时间分量
在这里,您可以看到调用 clear(((红线(所花费的时间在循环运行的 3 小时内从 0.055 秒增加到 0.7 秒。循环的所有其他组件几乎保持不变,除了对 plot(( 的调用有几个大的峰值。但是 clear(( 调用所花费的时间不断增加,是不可接受的。它很可能也是内存泄漏的罪魁祸首。
我已经在下面的程序中提取了与此问题相关的程序部分。这可以提取并在 python3 中运行。它测量在 while 循环的每个循环中调用 clear((、plot(( 和 draw(( 所需的时间,并将其动态绘制在屏幕上。您可以看到清除的呼叫正在缓慢增加。该程序还允许您查看系统中其他活动对执行这些调用的时间的影响。您可以看到,只需移动鼠标即可产生效果。尝试打开浏览器或播放视频。
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,问题消失了。