最近在用Python(Pyqt5)编写一个可以获取gpu信息(功耗、显存占用、利用率等)并将这些信息保存成csv文件的程序。在程序编写完成后,运行时却发现,随着程序的运行,所占用的内存每秒都会增加 0.3M,如下图的任务管理器中的任务 “Python(2)” 所示。
这是一个很致命的问题,尤其是对于一个用于记录信息的脚本来说,因为其需要长时间的运行。
下面来看看我的代码结构,我只展示主要的部分:
class GpuTest(QThread):
def __init__(self, *args, **kwargs):
super(GpuTest, self).__init__()
def run(self): # 这是一个线程
while running.is_set(): # 如果线程开启
# 此处省略一万行
# 检测一下当前系统的时间,并且看看是否已经创建了对应的csv文件(每1小时建一个新的csv文件记录显卡信息)
while current_hour == last_hour and running.is_set():
with open(csvpath, mode='a+', encoding='utf-8', newline="") as f: # 打开csv文件并准备写入显卡数据
csv_write = csv.writer(f)
pynvml.nvmlInit() # 显卡接口初始化
systime = datetime.datetime.now().strftime('%H:%M:%S') # 获取系统当前时间
gpuDeviceCount = pynvml.nvmlDeviceGetCount() # 获取 Nvidia GPU块数
time.sleep(1) # 等待1秒钟
# 下面的代码不重要,可以不用看了
# 下面就是读取显卡的相关数据和写入csv文件了
for i in range(0, gpuDeviceCount):
handle = pynvml.nvmlDeviceGetHandleByIndex(i) # 获取GPU i的handle,后续通过handle来处理
memoryInfo = pynvml.nvmlDeviceGetMemoryInfo(handle) # GPU显存信息:总显存、已用显存、剩余显存
gpuName = str(pynvml.nvmlDeviceGetName(handle), encoding='utf-8') # 显卡名称
gpuTemperature = pynvml.nvmlDeviceGetTemperature(handle, 0) # 显卡温度
gpuPower = pynvml.nvmlDeviceGetPowerUsage(handle) # 显卡功耗
gpuUtilRate = pynvml.nvmlDeviceGetUtilizationRates(handle).gpu # gpu核心满速使用率
gpuMemoryRate = pynvml.nvmlDeviceGetUtilizationRates(handle).memory # gpu内存读写满速使用率
time.sleep(1)
show_list = [systime, str(round(gpuPower/1000, 0)), str(gpuTemperature),
str(round(memoryInfo.used / memoryInfo.total*100, 0)) + '%',
str(gpuUtilRate) + '%', str(gpuMemoryRate) + '%']
csv_write.writerow(show_list)
pynvml.nvmlShutdown() # 关闭管理工具
我查阅了相关的资料,发现也有很多人遇到了Python脚本运行时内存越来越大的问题。如何解决这个问题呢,我首先按网上的方法进行了一些尝试。
在知乎上有个用户是这样解释的,大致意思就是说程序中有变量很占用内存,那么我们需要在每次循环后手动用 del 删除该变量,同时使用 gc collect():
我把所有变量都试了一遍,并没有解决问题,看来并不是变量的问题。
在这篇问题帖中有位兄弟说 “while循环 + sleep()” 的结构会导致脚本运行时内存越来越大。将结构修改为定时器的方式就可以解决。
我发现我的程序里也有这种 “while循环 + sleep()” 结构,因此我也尝试了这种方法,但是还是没有解决我的问题。看来我的程序问题也不在这。
我还试了许多其他的方法,但是都没有解决我的问题,就在我觉得没法解决时,我突然注意到我程序中的一个初始化函数貌似在线程中会运行很多次,如下所示。
pynvml.nvmlInit() # 初始化显卡管理工具
为了方便大家理解这句代码在整个代码中的位置,我将代码进行简化:
while 【是否启动线程】
while 【是否记录数据】
pynvml.nvmlInit() # 显卡接口管理工具初始化
# 接下来开始记录数据
# 省略一万行
sleep(1)
pynvml.nvmlShutdown() # 关闭管理工具
可以看到,显卡接口管理工具在每次要记录数据时都会进行初始化,然后在记录结束后再关闭;即使从程序的角度出发,这也是一种重复操作。将这句初始化代码移动到启动线程前,并且在线程运行时不关闭显卡管理工具,修改后的代码结构如下所示:
while 【是否启动线程】
pynvml.nvmlInit() # 显卡接口管理工具初始化
while 【是否记录数据】
# 接下来开始记录数据
# 省略一万行
sleep(1)
pynvml.nvmlShutdown() # 关闭管理工具
这样修改之后,问题被成功的解决了。