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

使用for循环后Python Matplotlib动画程序每隔1秒卡顿

解决Matplotlib动画for循环导致的卡顿问题

问题描述

原本运行正常的Python Matplotlib柱状图动画代码,使用for循环精简重复逻辑后出现以下问题:

  • 柱状图窗口每隔1秒卡顿1秒
  • 鼠标悬停图表、拖动窗口时,也会出现每隔1秒的周期性卡顿
  • 移除for循环后卡顿消失,但希望保留for循环的代码优化方式
    硬件配置:Intel Core i5 14th Gen,32GB内存

原因分析

  1. 重复轴清除与重绘:每帧动画都执行ax.clear(),完全清除轴的所有元素后重新创建柱状图,极大增加计算开销
  2. 冗余初始化操作:轴背景设置、关闭坐标轴等静态配置被放入动画函数内重复执行
  3. 高频IO与数据处理:每1秒重复读取3个CSV文件、执行两次数据合并、保存图片,IO和计算操作阻塞动画线程
  4. 重复布局计算:plt.tight_layout()在每帧都调用,额外增加布局计算开销

解决方案

  1. 复用柱状图对象:初始化时创建Bar对象,后续仅更新高度数据,避免重复创建图形
  2. 静态配置前置:将轴背景、关闭坐标轴等静态操作移到动画函数外,仅执行一次
  3. 优化数据读取:通过文件修改时间检查,仅在文件更新时重新读取,减少IO操作
  4. 降低非必要操作频率:若实时保存图片非必须,可降低保存频率或放到后台线程执行

优化后的代码

import os
from importlib import reload

import matplotlib.pyplot as plt
import pandas as pd
from matplotlib.animation import FuncAnimation

import cred


def run_script():
    # 全局配置初始化
    plt.rcParams["figure.facecolor"] = "#131722"
    plt.style.use("fivethirtyeight")
    fig, axs = plt.subplots(3, 1, figsize=(10, 8), sharex=True)

    # 静态背景配置,仅执行一次
    fig.patch.set_facecolor("#131722")
    for ax in axs:
        ax.set_facecolor("#131722")
        ax.axis("off")  # 静态配置无需每帧重复设置

    # 初始化文件路径与缓存变量
    fold = os.path.join(os.getcwd(), "data")
    symbols = [cred.symbol0, cred.symbol1, cred.symbol2]
    csv_paths = [os.path.join(fold, f"{s.split(':')[1]}.csv") for s in symbols]
    chart_path = os.path.join(os.getcwd(), "images", "chart2.png")
    
    # 缓存文件修改时间与数据
    last_modified = [0]*3
    cached_data = None
    bars = []

    def init_chart():
        """初始化图表元素,仅执行一次"""
        nonlocal cached_data, bars
        # 首次读取数据
        try:
            bankniftydata = pd.read_csv(csv_paths[0], skip_blank_lines=True)
            pedata = pd.read_csv(csv_paths[1], skip_blank_lines=True)
            cedata = pd.read_csv(csv_paths[2], skip_blank_lines=True)
        except Exception as e:
            print(f"初始化读取失败: {e}")
            return
        
        option_data = pd.merge(pedata, cedata, on="Time", how="inner")
        cached_data = pd.merge(bankniftydata, option_data, on="Time", how="inner")

        # 创建初始柱状图,保存Bar对象用于后续更新
        for idx, (symbol, ax) in enumerate(zip(symbols, axs)):
            vol_col = f"{symbol.split(':')[1]}Volume"
            bar = ax.bar(cached_data["Time"], cached_data[vol_col], color="#9598a1")
            bars.append(bar)
            # 设置静态标题框架
            ax.set_title(
                f"{symbol.split(':')[1]}   {cred.resolution}",
                loc="left",
                color="#9598a1",
                fontsize=12,
            )
        ax.autoscale(tight=True)
        plt.tight_layout()
        return bars

    def animatechart(i):
        nonlocal cached_data, last_modified
        reload(cred)

        # 检查文件是否更新,仅读取变化的文件
        need_update = False
        for idx, path in enumerate(csv_paths):
            current_mtime = os.path.getmtime(path)
            if current_mtime > last_modified[idx]:
                last_modified[idx] = current_mtime
                need_update = True
        
        if need_update:
            # 重新读取数据
            try:
                bankniftydata = pd.read_csv(csv_paths[0], skip_blank_lines=True)
                pedata = pd.read_csv(csv_paths[1], skip_blank_lines=True)
                cedata = pd.read_csv(csv_paths[2], skip_blank_lines=True)
            except Exception as e:
                print(f"读取数据失败: {e}")
                return bars
            
            option_data = pd.merge(pedata, cedata, on="Time", how="inner")
            cached_data = pd.merge(bankniftydata, option_data, on="Time", how="inner")

        # 更新柱状图数据,复用已有的Bar对象
        if cached_data is not None:
            for idx, (symbol, bar_container) in enumerate(zip(symbols, bars)):
                vol_col = f"{symbol.split(':')[1]}Volume"
                # 更新每个柱子的高度
                for bar, height in zip(bar_container, cached_data[vol_col]):
                    bar.set_height(height)
                # 更新标题的动态日期部分
                ax = axs[idx]
                base_title = ax.get_texts()[0].get_text().split("   ")[0]
                ax.set_title(
                    f"{base_title}   {cred.resolution}   {cred.from_date()}   {cred.to_date()}",
                    loc="left",
                    color="#9598a1",
                    fontsize=12,
                )
            axs[-1].autoscale(tight=True)
            plt.tight_layout()

        # 降低保存频率,每5帧保存一次
        if i % 5 == 0:
            plt.savefig(chart_path, facecolor="#131722", bbox_inches="tight")
        
        return bars

    ani = FuncAnimation(
        fig,
        animatechart,
        init_func=init_chart,
        interval=1000,
        cache_frame_data=False,
        blit=True  # 开启blit优化,只更新变化元素
    )
    plt.show()


run_script()

关键优化点说明

  • init_func初始化:图表静态创建、首次数据读取仅执行一次,避免重复操作
  • 复用Bar对象:通过bar.set_height()更新数据,大幅减少图形重建开销
  • 文件修改时间检查:仅在文件更新时读取数据,降低IO操作频率
  • 开启blit=True:Matplotlib仅重绘变化元素,提升渲染效率
  • 静态配置前置:轴背景、关闭坐标轴等操作仅执行一次,减少重复计算

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 07:12:17