You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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、红色填充区域等),导致内存持续累积,无法被垃圾回收机制释放。

具体修复步骤

  1. 每次绘图前清空子图
    在graph1函数的开头添加一行代码,清除子图上的所有旧元素:

    def graph1(df_combined):
        graphConf.clear()  # 先清空旧的绘图内容
        # 后续原有逻辑...
    

    这会重置子图的所有线条、标签、填充,释放对应的内存。

  2. 优化全局变量的使用(可选但更规范)
    全局变量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()
    
  3. 减少临时数据对象(辅助优化)

    • 避免直接修改原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()
      
  4. 强制垃圾回收(补充手段)
    在graph1函数末尾添加垃圾回收触发代码,帮助清理临时对象:

    import gc
    gc.collect()
    

内容的提问来源于stack exchange,提问作者Galorch

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 01:45:28