生成器使用项[n-1] + item [n]内存

时间:2014-02-14 18:41:14

标签: python memory-management

我有一个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 = Nonedel datagc.collect()什么都不做。

我非常确定生成器本身不会使内存加倍,因为否则它产生的单个大值会增加峰值使用量,而在相同的迭代中会出现一个大对象;它只是大型的连续物体。

如何保存记忆?

3 个答案:

答案 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()