Python跑循环时内存泄露的解决方法
Python跑循环时内存泄露
今天在用Tensorflow跑回归做测试时,仅仅需要循环四千多次 (补充说一句,我在个人PC上跑的)。运行以后,我就吃饭去了。等我回来后,Console窗口直接亮红了!!!
import numpy as np import pandas as pd import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D import tensorflow as tf import matplotlib.font_manager as fm myfont = fm.FontProperties(fname='C:/Windows/Fonts/simsun.ttc') sess = tf.Session() for j in range(0,4096): print('第' + str(j) + '次回归') ......
此处忘了截图,反正就是说Keras出现了什么什么错误。然后我就顺手重启了工程。
接着就瞪着屏幕,为什么跑一半出错了???
这个时候我发现电脑管家的小火箭好像有点‘暴躁',打开任务管理器一看,果然。。。
Python占用内存都快两个G了,但是平时跑没有占用这么多呀。我就猜想是不是因为跑循环,内存没有释放,导致最后溢出,然后code就崩了。
抱着猜想的心态等了一分钟,然后。。
WTF 飙到两千三百多兆的占用了。结论很明显了,就是没有释放内存!!!
这让我想起来一个月前我在集群上并行GPU跑LSTM时,出现了Out of memory,
这是2080ti呀,足足十一个G的内存,虽然当时我就用了一块显卡,但也不至于溢出吧。当时没有想到是这个问题,现在回想一下十有八九就是没有释放内存。
至于为什么没有释放内存,那就要从下面这个人出生的时候说起了—>
还记得那是一个月黑风高夜晚,天空电闪雷鸣。。。。。。
hhh,开个玩笑,这个人是Python之父,不过我觉得接下来我要说的Python垃圾收集机制或多或少和他有一定的关系。
Python垃圾收集机制
现在的高级语言如java,c#等,都采用了垃圾收集机制,而不再是c,c++里用户自己管理维护内存的方式。自己管理内存极其自由,可以任意申请内存,但如同一把双刃剑,为大量内存泄露,悬空指针等bug埋下隐患。
对于一个字符串、列表、类甚至数值都是对象,且定位简单易用的语言,自然不会让用户去处理如何分配回收内存的问题。
python里也同java一样采用了垃圾收集机制,不过不一样的是:
python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略。
Python中的内存管理过程非常简单。Python通过保持对每个对象在程序中的引用计数来处理其对象,这意味着每个对象存储在程序中被引用的次数。此计数随程序运行时更新,并且当计数为零时,这意味着程序不再可访问该计数。因此,解释器可以回收和释放该对象的内存。
class User(object): def __del__(self): print("No reference left for {}".format(self)) user1 = User() user2 = user1 user3 = user1
在此示例中,我们制作了一个类和3个引用变量指向同一对象。让我们将其可视化:
现在,让变量user1,user2和user3指向None而不是User实例。
>>> user1 = None >>> user2 = None >>> user3 = None No reference left for <__main__.User object at 0x212bee9d9>
通过以上代码,引用已更改为:
将最后一个变量分配user3给后None,该对象将被垃圾回收,这将调用该__del__函数。
从根本上讲,每当引用对象时,Python对象的引用计数都会增加,而在取消引用对象时,Python的引用计数会减少。如果对象的引用计数为0,则将释放该对象的内存。您程序的代码无法禁用Python的引用计数。
python跑循环为什么没有释放内存
有些人认为,引用计数是A poor man's garbage collector 。很大一部分原因是它存在一些缺点,其中就包括无法检测到循环应用。
如果在循环引用中的对象定义了__del__函数,那么在循环引用中Python解释器无法判断析构对象的顺序,因此就不做处理,python gc不能进行回收。
解决办法
在提出解决问题之前,我们要先找到问题。因为我这个Code需要画图,然后保存到本地。我就想是不是画的图导致占用过多内存。
F_max = max(y_result) plt.figure(figsize=(15, 5)) plt.subplot(131) plt.scatter(Yp_data, Yt_data, alpha=0.8) plt.title(u'回归前结果',fontproperties=myfont) plt.plot([0,F_max], [0,F_max], color = 'r') plt.subplot(132) plt.scatter(y_result, Yt_data, alpha=0.8) plt.title(u'回归后结果',fontproperties=myfont) plt.plot([0,F_max], [0,F_max], color = 'r') plt.subplot(133) plt.plot(loss_vec, 'k-') plt.title('loss per Generation') plt.xlabel('Generation') plt.ylabel('Loss') plt.savefig('D:/lwt/py/Regression/figure/{} .png'.format(_type), dpi=100) plt.show()
有想法你就要去做是吧,然后我就加了两行代码。
plt.close('all') plt.clf()
这两句是用来清除内存中的图像和清理掉 axes,并且为了尽可能的减少内存占用,把plt.show()
都删除了
然后运行Code,打开任务管理器,测试是否管用。
貌似python占用的内存是没有之前上升的那么快了,不知道是不是心理作用hh。反正效果不明显,看来得从别的地方下手了。
排除掉这个可能,那就是回收机制的锅了。既然你不主动,那我就主动点咯(猿式阴笑嘿嘿)。
for x in list(locals().keys())[:]: del locals()[x] gc.collect()
for循环就不要过多解释了,先说del
语句的用法
del obj_name
这del
是一个Python关键字。而且,obj_name
可以是变量,用户定义的对象,列表,列表中的项,字典等。可以用来删除用户定义的对象;删除变量,列表和字典;从列表中删除项目和切片;从字典中删除键等等。具体用法大家可以查找相关文档了解。
gc是python的垃圾回收器接口,gc.collect(generation=2)
若被调用时不包含参数,则启动完全的垃圾回收。可选的参数 generation 可以是一个整数,指明需要回收哪一代(从 0 到 2 )的垃圾。当参数 generation 无效时,会引发 ValueError 异常。返回发现的不可达对象的数目。每当运行完整收集或最高代 (2) 收集时,为多个内置类型所维护的空闲列表会被清空。 由于特定类型特别是 float 的实现,在某些空闲列表中并非所有项都会被释放。
实测,有明显的效果,内存占用上升的速度明显减小了,不过总体还是承上升的趋势。相比之前,好太多了有没有。
方法总比困难多,解决内存泄漏也还有其他的办法。我们还可以用objgraph、weakref等工具来分析并解决内存泄露、循环引用问题。实在不行,还有一个超级硬核的办法,自己动手写一个腾讯电脑管家小火箭的脚本,时不时的让它上上天(腾讯记得打钱啊啊啊)。哈哈哈
在任何环境,不管是服务器,客户端,内存泄露都是非常严重的事情。如果是线上服务器,那么一定得有监控,如果发现内存使用率超过设置的阈值则立即报警,尽早发现些许还有救。
新的问题
不要强行收集垃圾太多次。这是因为,即使要释放内存,仍然需要花费时间来评估对象是否符合垃圾收集条件。我在实测中的确发现运行速度有些下降。
还有就是他会把全局的变量都删了,以至于出现np
和pd
等导入的包都not defined。我的建议是把循环写到函数里,做到只对某个函数起作用。
笔者作为一个学生也是刚接触python不久,如有不对的地方还请指正,谢谢~
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持【听图阁-专注于Python设计】。