强制Python释放对象以释放内存

3

我正在运行以下代码:

from myUtilities import myObject
for year in range(2006,2015):
    front = 'D:\\newFilings\\'
    back = '\\*\\dirTYPE\\*.sgml'
    path = front + str(year) + back
    sgmlFilings = glob.glob(path)
    for each in sgmlFilings:
        header = myObject(each)
        try:
            tagged = header.process_tagged('G:')
        except Exception as e:
            outref = open('D:\\ProblemFiles.txt','a')
            outref.write(each '\n')
            outref.close()
            print each

如果我从重启开始,Python的内存分配/消耗相当小。然而随着时间的推移,它会显著增加,最终在大约一天后我几乎没有剩余的空闲内存(安装了24GB [294 mb空闲23960缓存]),Windows任务管理器列表中Python占用的内存为3GB。我正在观察这个数字,在三天内对文件集合运行代码时增长。

我原以为,由于我正在进行所有操作

tagged = header.process_tagged('G:')

每个循环关联的内存都将被释放和垃圾回收。有什么方法可以强制释放这些内存吗?尽管我还没有运行统计数据,但通过观察磁盘上的活动,我可以看出随着时间的推移(以及内存块变得越来越大),该进程会变慢。
编辑:我查看了下面引用的问题,我认为它们与我的问题不同。在另一个问题中,他们抓住了对象(三角形列表)并需要整个列表进行计算。每次循环时,我都会读取文件,对文件进行一些处理,然后将其写回磁盘。然后我再读取下一个文件...
关于可能的内存泄漏,我正在使用myObject中的LXML。
请注意,自从第一次提问以来,我添加了从MyUtilities导入myObject的代码。MyUtilities包含完成所有操作的代码。
关于发布我的myUtilities代码 - 那会远离基本问题 - 在每次迭代后,我都会完成标题和标记,标记会执行操作并将结果写入另一个驱动器,实际上是新格式化的驱动器。
更新-所以,在myObject类中有一种情况,其中一个文件是用以下方式打开的:
myString = open(somefile).read()
我将其更改为
with open(somefile,'r') as fHandle:
`    myString = fHandle.read()`

抱歉,格式有些混乱——我还在努力学习中。

但是这似乎没有明显的影响。当我开始一个新的循环时,我的Cached内存有4000 mb,经过22分钟和处理27K个文件后,我大约有26000 mb的Cached内存。

我感谢下面所有的答案和评论,并一整天阅读并测试各种内容。如果这项任务本来需要一周时间,现在看起来可能需要一个月以上。

我一直得到有关代码其余部分的问题。然而,它超过800行,对我来说那样就不太与中心问题相关了。

所以先创建一个myObject实例,然后将myObject中包含的方法应用于header。

这基本上是文件转换。一个文件被读入,然后复制文件的某些部分并写入磁盘。

对我来说,中心问题是header或tagged显然具有某种持久性。在开始下一个周期之前,如何处理与header或tagged相关的所有内容。

我现在已经运行了代码14个小时左右。第一个周期的处理大约需要22分钟,处理27K个文件,现在处理大约相同数量需要一个半小时左右。

仅运行gc.collect是不起作用的。我停止了程序并在解释器中尝试了一下,但没有看到内存统计数据有任何变化。

编辑后阅读了下面的memoryallocator说明,我认为与缓存相关的数量不是问题——而是运行python进程所占用的数量。所以新的测试是从命令行运行代码。我将继续观察和监控,并在看到结果后发布更多内容。

编辑:仍在努力中,但已经设置了代码从一个bat文件开始运行,并带有sgmlFilings的一个循环中的数据(见上文)。批处理文件如下:

python batch.py
python batch.py
 .
 .
 .

batch.py首先读取一个队列文件,该文件包含要glob的目录列表,它将第一个目录从列表中取出,更新列表并保存,然后运行headertagged进程。虽然有点笨拙,但是由于每次迭代后python.exe都会关闭,因此python不会累积内存,因此该过程以一致的速度运行。


https://dev59.com/BXM_5IYBdhLWcg3wlEPO - Colonel Beauvel
1
我建议你发布你的所有代码(这里有一些函数的源对我们不可见)。然后我们可以分析你的所有代码,尝试最小化内存占用。 - inspectorG4dget
你正在使用哪些库?如果你正在使用一些用C实现的库,它可能会有一些内存泄漏问题... - Bakuriu
恐怕这个问题太宽泛了,除非你告诉我们你的处理代码实际在做什么。 - Burhan Khalid
4个回答

12
原因在于CPython的内存管理。Python管理内存的方式使得长时间运行的程序很难处理。当您使用del语句显式释放对象时,CPython不会将分配的内存返回给操作系统。它保留内存以供将来使用。解决这个问题的一种方法是使用多进程模块,在完成作业后杀死进程并创建另一个进程。这样可以强制清空内存,操作系统必须释放该子进程使用的内存。 我曾经遇到过完全相同的问题。随着时间的推移,内存使用量过度增加,甚至导致系统不稳定和无法响应。我使用了一种不同的技术,利用信号和psutil来处理它。当你有一个循环并需要在堆栈上分配和释放数据时,例如,通常会出现这个问题。
您可以在此处阅读有关Python内存分配器的更多信息: http://www.evanjones.ca/memoryallocator/ 此工具对于分析内存使用也非常有帮助:https://pypi.python.org/pypi/memory_profiler 还有一件事,将slots添加到myObject中,似乎您已经在对象内部修复了slots,这也有助于减少内存使用。没有指定slots的对象会分配更多的内存以处理后续可能添加的动态属性:http://tech.oyster.com/save-ram-with-python-slots/

1
谢谢你提供了一些有用的信息 - 我还在阅读,但我认为slots不是答案,因为我一次只创建一个对象。 - PyNEwbie
1
为什么不使用多进程模块?有一个很好的池类可以在两个方面具有优势:1)释放内存是有保证的,因为子进程将在完成作业后终止;2)利用所有可用的 CPU 核心,您可以并行迭代文件。 - mitghi
我会尝试,但我想知道I/O是否是一个更大的问题,所以我正在研究这个问题。 - PyNEwbie
2
只要Python重复使用已释放但未映射内存,它不会向操作系统释放内存就不应该有问题。特别是,内存使用量应该增长到某个数量然后稳定下来;它不应该无限制地增加,这似乎是在这里发生的情况。 - icktoofay

2
您可以使用 gc 模块来强制进行垃圾回收。特别地,可以使用 gc.collect() 函数。
然而,这可能无法解决您的问题,因为很可能 gc 正在运行,但您正在使用包含内存泄漏的库/代码,或者该库/代码将某些引用保留在某个地方。无论如何,我怀疑 gc 不是这里的问题。
有时候,您可能会有一些代码,它会保持对您不需要的对象的引用。在这种情况下,当不再需要这些对象时,您可以考虑显式地删除它们,但这似乎不是这种情况。
此外,请记住,Python 进程的内存使用量实际上可能比操作系统报告的要小得多。特别地,调用 free() 不需要将内存返回给操作系统(通常在执行小型分配时不会发生这种情况),因此您看到的可能是到目前为止内存使用的最高峰值,而不是当前使用情况。加上 Python 在 C 的内存分配之上使用了另一层内存分配,这使得对内存使用情况进行分析变得非常困难。然而,如果内存不断增加,这可能不是这种情况。
您应该使用类似于 Guppy 的工具来分析内存使用情况。

1
你可以使用gc模块来控制这些内容。具体来说,你可以尝试将其整合进去。
gc.collect() 

在您的循环体中。

2
gc.collect()并不总是将内存返回给操作系统。我曾经遇到过完全相同的问题,但它并没有起到帮助作用。 - mitghi
1
@mitghi 我同意你不能像在C中那样控制它。然而,我见过一些情况它确实有帮助(也听说过像你说的不行的情况)。你的情况可能会有所不同。 - Ami Tavory

1

在采取强制垃圾收集(从未是一个好主意)之前,请尝试以下基本操作:

  1. 使用 glob.iglob(生成器),而不是一次性获取所有文件的列表。

  2. 在您的 myObject(each) 方法中,确保关闭文件或使用 with 语句 自动关闭;否则它将继续占用内存空间。

  3. 不要打开和关闭文件;只需在异常处理程序中打开文件进行一次写入。

由于您没有发布实际进行处理的代码(因此可能是您内存问题的原因),因此很难推荐具体措施。


这个程序已经运行了三天,其中200万次迭代中出现了400个异常。我感觉需要更新日志,因为如果程序崩溃,我就必须重新开始查找异常——除非打开文件,否则无法识别异常。虽然我很感激关于 iglob 的评论(我不知道它的存在),但列表中只有40K或50K个项目,所以不会占用太多内存。 - PyNEwbie
事实是,在处理过程中,我打开了两个文件,其中一个我使用了with语句,而另一个则是myString = open(someFile).read()。我从来没有理解过它们之间的区别,也没有找到任何Python读取文件的例子使用了with语句。所以我认为这看起来很有前途。 - PyNEwbie
myString = open(someFile).read() <- 这将整个文件加载到内存中 - 更重要的是,文件处理程序留给Python以后处理。除非必须,否则不要这样做。 - Burhan Khalid
我必须将整个文件存储在内存中,问题在于随着时间的推移,累积的数据量越来越大,而并非特定的文件。我现在正在寻找一些有关此问题的见解。我已经将代码更改为“with”,不幸的是,需要花费10到12小时才能知道它是否可以解决问题。 - PyNEwbie
关闭文件是一个好的实践,但如果你没有这样做,当所有对该文件的引用都消失时,垃圾回收器会处理它,此时终结器将运行并关闭文件,因此它实际上已经被关闭了,所以这不是问题的根源。 - icktoofay

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