为什么我使用这个Python循环会出现内存泄漏?

13

我正在编写一个自定义文件系统爬虫,它通过sys.stdin传递了数百万个glob进行处理。我发现当运行脚本时,其内存使用量随时间的推移而急剧增加,整个过程几乎无法进行。我在下面编写了一个最小化的示例来显示问题。我是做错了什么,还是发现了Python / glob模块中的错误?(我正在使用python 2.5.2)。


#!/usr/bin/env python
import glob
import sys
import gc

previous_num_objects = 0

for count, line in enumerate(sys.stdin):
   glob_result = glob.glob(line.rstrip('\n'))
   current_num_objects = len(gc.get_objects())
   new_objects = current_num_objects - previous_num_objects

   print "(%d) This: %d, New: %d, Garbage: %d, Collection Counts: %s"\
 % (count, current_num_objects, new_objects, len(gc.garbage), gc.get_count())
   previous_num_objects = current_num_objects
输出结果如下:
(0) This: 4042, New: 4042, Python垃圾:0,Python收集计数:(660,5,0)
(1) This: 4061, New: 19, Python垃圾:0,Python收集计数:(90,6,0)
(2) This: 4064, New: 3, Python垃圾:0,Python收集计数:(127,6,0)
(3) This: 4067, New: 3, Python垃圾:0,Python收集计数:(130,6,0)
(4) This: 4070, New: 3, Python垃圾:0,Python收集计数:(133,6,0)
(5) This: 4073, New: 3, Python垃圾:0,Python收集计数:(136,6,0)
(6) This: 4076, New: 3, Python垃圾:0,Python收集计数:(139,6,0)
(7) This: 4079, New: 3, Python垃圾:0,Python收集计数:(142,6,0)
(8) This: 4082, New: 3, Python垃圾:0,Python收集计数:(145,6,0)
(9) This: 4085, New: 3, Python垃圾:0,Python收集计数:(148,6,0)
每100次迭代,会释放100个对象,因此每100次迭代,len(gc.get_objects())会增加200。而len(gc.garbage) 保持不变为0。二代收集计数缓慢增加,而0代和1代的计数则上下波动。

1
这会积累大量未收集的对象。但是,它不会停滞不前,对吧?你能否编写一个类似的小脚本,使其实际上停止运行吗? - S.Lott
2个回答

7
我追踪到了fnmatch模块。glob.glob调用fnmatch来执行globbing,并且fnmatch具有一个正则表达式缓存,该缓存从未被清除。因此,在这种情况下,缓存不断增长而没有受到检查。我已经针对fnmatch库提交了一个错误报告[1]。
[1]: http://bugs.python.org/issue7846 Python Bug

我真不知道我怎么能在re模块中找到相似的缓存,却没有找到这个!也许我应该扣掉自己回答的一分…… - mzz

2
我无法在我的系统上重现实际的内存泄漏,但我认为你的“每100次迭代释放100个对象”是你通过glob模块命中编译正则表达式的缓存。如果你查看re.py,你会看到_MAXCACHE默认为100,并且默认情况下,在_compile中达到这个值时整个缓存都会被清除。如果在gc调用之前调用re.purge(),你可能会看到这种影响消失。
(请注意,我只建议在这里使用re.purge()来检查缓存是否影响了你的gc结果。在你的实际代码中不需要这样做。)
我怀疑这并不能解决你的大量内存增加的问题。

谢谢你的建议 - 当我按照你所说的做时,效果确实消失了,每个循环的新对象数量变成了2。虽然这并没有解决内存增加的问题,但肯定会帮助理解发生了什么。 - Andy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接