树莓派运行Matplotlib实时数据动画出现内存溢出如何解决
内存不足问题根因
- 全局
xs列表持续泄漏:你在animate函数内执行xs = ['T-9'...'Now']属于局部变量重新赋值,并没有修改外部全局的xs列表,外部列表每次调用append(i)后没有做截断,会无限变长,占用内存持续升高。 - 重复创建绘图对象:每次执行
ax.clear()后重新调用plot()生成新的线条对象,旧的线条对象没有被及时回收,长期运行会累积大量无效绘图对象。 - 不必要的内存操作:频繁调用
gc.collect()本身也会占用额外资源,优化对象复用后无需手动触发垃圾回收。
核心疑问解答
- 能不能关闭绘图窗口还保留实时数据展示功能?
实时可视化展示必须依赖绘图窗口渲染,如果不需要可视化只需要后台接收存储数据,可以直接删除所有matplotlib相关逻辑,只保留串口读取和数据落盘的代码即可,内存占用会大幅降低。如果需要保留可视化能力,就不能关闭绘图窗口,但可以通过优化渲染逻辑把内存占用稳定在极低水平。
- 能不能每次执行animate函数时新建并关闭一个新的figure?
完全不建议这么做,新建、销毁figure的开销远高于复用同一个figure,反而会大幅提升内存占用和CPU消耗,加剧卡顿和内存不足问题,正确做法是复用同一个figure和绘图对象,仅更新数据内容。
优化后可稳定运行的代码
import serial import matplotlib matplotlib.use('TkAgg') import matplotlib.pyplot as plt import matplotlib.animation as animation # 初始化绘图对象,仅创建一次 fig, axRT1 = plt.subplots() # 固定x轴标签,不需要动态append xs = ['T-9','T-8','T-7','T-6','T-5','T-4','T-3','T-2','T-1','Now'] # 初始化线条对象,后续仅更新y轴数据 lineAC, = axRT1.plot(xs, [0]*10, 'b:', label='Mains', linewidth = 4) lineDC, = axRT1.plot(xs, [0]*10, 'g--', label='Solar', linewidth = 4) axRT1.legend() # 固定y轴范围避免每次重算,可根据实际数据范围调整 axRT1.set_ylim(0, 100) ysAC = [] ysDC = [] ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=1) ser.flush() def getValues(): if ser.in_waiting > 0: line = ser.readline().decode('utf-8').rstrip() return list(map(float, line.split(","))) # 提前转成数值类型 return None def animate(i): values = getValues() if not values: return lineAC, lineDC wAC = values[1] wDC = values[2] # 仅维护最近10条数据 ysAC.append(wAC) ysDC.append(wDC) ysAC[:] = ysAC[-10:] ysDC[:] = ysDC[-10:] # 数据不足10条时直接返回,够了再更新线条数据 if len(ysDC) == 10: lineAC.set_ydata(ysAC) lineDC.set_ydata(ysDC) # 开启blit后只返回需要更新的对象,渲染效率提升数倍 return lineAC, lineDC # 开启blit,去掉不需要的参数 ani = animation.FuncAnimation(fig, animate, interval=1000, blit=True) plt.get_current_fig_manager().full_screen_toggle() plt.show()
额外优化建议
- 可以根据你的实际串口数据范围调整
axRT1.set_ylim()的上下限,避免动态计算轴范围的额外开销。 - 如果不需要全屏展示可以去掉全屏相关代码,进一步降低渲染开销。
- 树莓派如果性能较低,可以把
interval调大到2000(2秒刷新一次),进一步降低资源占用。
内容的提问来源于stack exchange,提问作者Jak
相关产品推荐
相关产品推荐

