我有一个Python 3.x程序,可以处理几个包含大量数据的大型文本文件,这些文件偶尔可以刷掉我的微不足道的工作站的内存限制。从一些基本的内存分析来看,似乎在使用生成器时,我的脚本的内存使用量可以保存连续的元素,使用的内存量是我预期的两倍。
我做了一个简单的,独立的例子来测试生成器,我在Python 2.7,3.3和3.4中得到了类似的结果。我的测试代码如下,memory_usage()
是this function from an SO question的修改版本,使用/proc/self/status
并在观看时与top
达成一致。 resource
可能是一种更跨平台的方法:
import sys, resource, gc, time
def biggen():
sizes = 1, 1, 10, 1, 1, 10, 10, 1, 1, 10, 10, 20, 1, 1, 20, 20, 1, 1
for size in sizes:
data = [1] * int(size * 1e6)
#time.sleep(1)
yield data
def consumer():
for data in biggen():
rusage = resource.getrusage(resource.RUSAGE_SELF)
peak_mb = rusage.ru_maxrss/1024.0
print('Peak: {0:6.1f} MB, Data Len: {1:6.1f} M'.format(
peak_mb, len(data)/1e6))
#print(memory_usage()) #
data = None # go
del data # away
gc.collect() # please.
# def memory_usage():
# """Memory usage of the current process, requires /proc/self/status"""
# # https://stackoverflow.com/a/898406/194586
# result = {'peak': 0, 'rss': 0}
# for line in open('/proc/self/status'):
# parts = line.split()
# key = parts[0][2:-1].lower()
# if key in result:
# result[key] = int(parts[1])/1024.0
# return 'Peak: {peak:6.1f} MB, Current: {rss:6.1f} MB'.format(**result)
print(sys.version)
consumer()
在实践中,我将处理来自此类生成器循环的数据,保存我需要的内容,然后将其丢弃。
当我运行上面的脚本,并且两个大元素串联起来(数据大小可以高度变化)时,似乎Python在释放前一个之前计算下一个,导致内存使用量增加一倍。
$ python genmem.py
2.7.3 (default, Sep 26 2013, 20:08:41)
[GCC 4.6.3]
Peak: 7.9 MB, Data Len: 1.0 M
Peak: 11.5 MB, Data Len: 1.0 M
Peak: 45.8 MB, Data Len: 10.0 M
Peak: 45.9 MB, Data Len: 1.0 M
Peak: 45.9 MB, Data Len: 1.0 M
Peak: 45.9 MB, Data Len: 10.0 M
# ^^ not much different versus previous 10M-list
Peak: 80.2 MB, Data Len: 10.0 M
# ^^ same list size, but new memory peak at roughly twice the usage
Peak: 80.2 MB, Data Len: 1.0 M
Peak: 80.2 MB, Data Len: 1.0 M
Peak: 80.2 MB, Data Len: 10.0 M
Peak: 80.2 MB, Data Len: 10.0 M
Peak: 118.3 MB, Data Len: 20.0 M
# ^^ and again... (20+10)*x
Peak: 118.3 MB, Data Len: 1.0 M
Peak: 118.3 MB, Data Len: 1.0 M
Peak: 118.3 MB, Data Len: 20.0 M
Peak: 156.5 MB, Data Len: 20.0 M
# ^^ and again. (20+20)*x
Peak: 156.5 MB, Data Len: 1.0 M
Peak: 156.5 MB, Data Len: 1.0 M
疯狂的腰带和吊带和导管胶带方法data = None
,del data
和gc.collect()
什么都不做。
我非常确定生成器本身不会使内存加倍,因为否则它产生的单个大值会增加峰值使用量,而在相同的迭代中会出现一个大对象;它只是大型的连续物体。
如何保存记忆?
答案 0 :(得分:1)
问题在于发电机功能;特别是在声明中:
data = [1] * int(size * 1e6)
假设您在 data 变量中有旧内容。当你运行这个语句时,它首先计算结果,因此你在内存中有2个这样的数组;新旧。只有这样才将 data 变量更改为指向新结构并释放旧结构。尝试将迭代器函数修改为:
def biggen():
sizes = 1, 1, 10, 1, 1, 10, 10, 1, 1, 10, 10, 20, 1, 1, 20, 20, 1, 1
for size in sizes:
data = None
data = [1] * int(size * 1e6)
yield data
答案 1 :(得分:0)
您是否尝试过使用gc模块?你可以在循环之间get a list of the objects that still reference your large data,检查它是否在list of unreachable but unfreed objects中,或启用一些调试标志。
幸运的是,在每次循环后对gc.collect()
进行简单调用可能会将您的问题排成一行。
答案 2 :(得分:0)
而不是:
data = [1] * int(size * 1e6)
#time.sleep(1)
yield data
尝试:
yield [1] * int(size * 1e6)
问题很简单,生成器的data
局部变量保留对已放置列表的引用,防止它在生成器恢复并丢弃引用之前被垃圾收集。
换句话说,在生成器外部执行del data
对垃圾收集没有影响,除非它是对数据的唯一引用。避免在生成器内部引用是真的。
如果你必须操纵数据,首先,你可以使用这样的hack在放弃引用之前删除它:
data = [1] * int(size * 1e6)
# ... do stuff with data ...
# Yield data without keeping a reference to it:
hack = [data]
del data
yield hack.pop()