Matplotlib结合Tkinter绘图函数的内存膨胀问题排查
内存泄漏问题:多次调用置信区间绘图函数导致内存飙升
我基于置信区间折线图的实现逻辑做了一个工具:让用户选择包含规范格式文件的文件夹,合并所有文件为一个大型DataFrame后传入graph1函数绘图。但目前遇到内存问题:每次调用graph1时内存都会大幅上涨(100+文件、10万+数据点的场景下,每次调用内存增长约30MB)。我尝试过清除子图及所有绘图相关内容,但没有效果;也排查过数据合并函数,多次调用该函数并未导致内存膨胀,希望有人能指出问题所在。
代码如下:
def menu(): confGUI = tk.Tk() confGUI.geometry("800x800") global figConf, graphConf, canvasConf figConf, graphConf = plt.subplots() canvasConf = FigureCanvasTkAgg(figConf, confGUI) canvasConf.get_tk_widget().pack() graph1(dataThatWasMadeElsewhere) confGUI.mainloop() def graph1(df_combined): # 归一化时间到0-1范围 df_combined['time'] = (df_combined['time']-np.min(df_combined['time']))/(np.max(df_combined['time'])-np.min(df_combined['time'])) df_grouped = df_combined.groupby('time')['y'].agg(['mean', 'std']) df_grouped.reset_index(inplace=True) median_std = df_grouped['std'].median() df_grouped['std'].fillna(median_std, inplace=True) # 将NaN的标准差替换为中位数标准差 # 对均值做滑动平均平滑,避免图表显示杂乱 window_size = 100 df_grouped['rolling_mean'] = df_grouped['mean'].rolling(window_size, center=True).mean() df_grouped['lower'] = df_grouped['mean']-df_grouped['std'] df_grouped['upper'] = df_grouped['mean']+df_grouped['std'] graphConf.set_xlabel('Time') graphConf.set_ylabel('Y') graphConf.fill_between(df_grouped['time'], df_grouped['lower'], df_grouped['upper'], color='red', alpha=0.2) # 绘制每个文件的单独曲线 for filename in set(df_combined['filename']): file_data = df_combined[df_combined['filename'] == filename] graphConf.plot(file_data['time'], file_data['Y'], color="gray", linewidth=0.5) graphConf.plot(df_grouped['time'], df_grouped['rolling_mean'], color='r', alpha=1, linewidth=5) canvasConf.draw() if __name__ == "__main__": menu()
问题分析与解决方案
核心问题
每次调用graph1时,你没有清空子图graphConf上的旧绘图元素,而是直接在原有内容上叠加新的线条、填充层。Matplotlib会保留所有这些旧的绘图对象(比如每个文件对应的灰色Line2D、红色填充区域等),导致内存持续累积,无法被垃圾回收机制释放。
具体修复步骤
每次绘图前清空子图
在graph1函数的开头添加一行代码,清除子图上的所有旧元素:def graph1(df_combined): graphConf.clear() # 先清空旧的绘图内容 # 后续原有逻辑...这会重置子图的所有线条、标签、填充,释放对应的内存。
优化全局变量的使用(可选但更规范)
全局变量figConf、graphConf、canvasConf会长期持有绘图对象引用,增加内存泄漏风险。建议改成通过函数参数传递:def menu(): confGUI = tk.Tk() confGUI.geometry("800x800") figConf, graphConf = plt.subplots() canvasConf = FigureCanvasTkAgg(figConf, confGUI) canvasConf.get_tk_widget().pack() # 把绘图对象传入graph1 graph1(dataThatWasMadeElsewhere, graphConf, canvasConf) confGUI.mainloop() def graph1(df_combined, graphConf, canvasConf): graphConf.clear() # 原有绘图逻辑... canvasConf.draw()减少临时数据对象(辅助优化)
- 避免直接修改原DataFrame:
df_combined['time'] = ...会修改输入数据,改用assign创建新对象(如果不需要保留原数据则可忽略):df_combined = df_combined.assign( time=(df_combined['time']-df_combined['time'].min())/(df_combined['time'].max()-df_combined['time'].min()) ) - 链式调用分组聚合,减少中间变量:
df_grouped = df_combined.groupby('time')['y'].agg(['mean', 'std']).reset_index()
- 避免直接修改原DataFrame:
强制垃圾回收(补充手段)
在graph1函数末尾添加垃圾回收触发代码,帮助清理临时对象:import gc gc.collect()
内容的提问来源于stack exchange,提问作者Galorch
相关产品推荐
相关产品推荐

