python递归yield以减少内存占用

时间:2015-09-11 18:08:51

标签: python recursion yield

我有一个类似下面的函数,递归地将一个大数组拆分成两个子数组,并收集所有这些数据以供将来处理。我的问题是,如果有一种方法可以在分裂过程中产生子阵列以减少内存占用,例如,分割调用的数组很大,~50G。

def split(array, subarrays):
    n = len(array)
    if n == 1:
        return
    else:
        i = n / 2
        subarray1 = array[:i]
        subarrays.append(subarray1)
        subarray2 = array[i:]
        subarrays.append(subarray2)
        split(subarray1, subarrays)
        split(subarray2, subarrays)
        return 

subarrays = []
# In production, range(10) will be replaced with a huge array, e.g. 50G
split(range(10), subarrays)
for i in subarrays:
    print i
    # do some other stuff with each subarray

4 个答案:

答案 0 :(得分:2)

您可以尝试使用memoryview,Eli Bendersky已经为此写了一个不错的blog entry

我会尽力总结一下。在对象上创建内存视图时,您正在创建对存储对象的内存中的(ctype)数据结构的引用。 memoryview slice是在此数据结构中查找某些值的参考。您可以在同一个底层结构上创建多个视图,而无需复制任何内容。这就像切片列表或数组一样。

你的数据必须支持缓冲协议(numpy数组和bytearrays这样做,但是列表没有)。

我认为添加这一行就足够了

memview = memoryview(yourarray)

代码并将其传递给split而不是数组。

请注意两件事:

  • 您正在处理一个大数组,因此对数组的一部分(在一个切片中制作)的更改会传播到覆盖此值的所有其他切片。
  • 您的结果现在是memoryview对象。要打印它们,您需要先将它们(例如列表)投射出来。

示例:

>>> memview = memoryview("abcde")
>>> print memview
<memory at 0xfoo>
>>> print list(memview)
['a', 'b', 'c', 'd', 'e']

>>> mv_slice = memview[3:]
>>> print list(mv_slice)
['d', 'e']

>>> mv_slice[0] = 'y'
>>> print list(mv_slice)
['y', 'e']

>>> print list(memview)
['a', 'b', 'c', 'y', 'e']
# note that the change propagated to the main memoryview

所有这一切当然都假定,你可以在一个点上加载50GB的内存。如果你不能这样做,你应该看一下mmap模块。

编辑 - numpy字符串数组

  

memoryview是否可以使用numpy字符串数组?

     

似乎没有。例如memview = memoryview(np.array(["abcde", 'aa']))memview[0] is 'abcde',但memview[1]'aa\x00\x00\x00'

嗯,从技术上来说它确实有效。它只显示numpy如何存储字符串数组。那就是:非常糟糕;)

如果你创建一个像这样的numpy字符串数组:

>>> npa = np.array(["abcde", 'aa'])
>>> print repr(npa)
array(['abcde', 'aa'],
  dtype='|S5')

你看到dtype是|S5,意思是长度为5的字符串。较短字符串的'缺失'位置用空(零)字节填充(\x00)(numpy通常隐藏为方便起见)。这是因为numpy使用连续的2D数组将字符串存储在内存中,以实现真正快速的随机访问。

这意味着,数组中的所有条目都会消耗尽可能多的字符串。

strings = ["foobar"*100000] + ["f" for _ in xrange(10000)]
huge_npa = np.array(strings, dtype=str)

它包含一个非常长的字符串(600,000个字符,每个1个字节)和10.000个字符串,只有1个字节。所以总内存消耗应该在600KB左右。如果你创建了这个数组,虽然它占用了 6GB 的内存。

Expected:
1 string * 6 bytes * 100.000 => 600.000 * 1 byte = 600 KB
10.000 strings * 1 byte      =>  10.000 * 1 byte =  10 KB
total                                              610 kB

Reality:
10.000 strings * 6 bytes * 10.0000 => 6.000.000.000 * 1 byte = 6 GB

如果你的字符串大小差别很大,你可能会浪费很多内存。也许你应该重新考虑使用numpy数组。

答案 1 :(得分:1)

这实际上会增加内存占用。每次切片列表时,除了旧列表外,还会得到一个新列表。

e.g:

l = [1, 2, 3, 4]  # great, we have 4 references to objects in this list
l2 = l[:2]        # ok, now we have an additional list with 2 more references

你真正想做的是以块的形式读取原始数据。

答案 2 :(得分:1)

我不确定你在这里想要实现的目标。是的,您可以使用yield逐个返回子数组。但是它们不会按排序顺序排列,分割过程仍会使内存使用量大致翻倍。但我认为这比将其增加35倍更好,这就是使用50G列表中的代码所发生的事情。

def split(array):
    n = len(array)
    if n == 1:
        return
    else:
        i = n // 2
        subarray1 = array[:i]
        subarray2 = array[i:]
        yield subarray1
        yield subarray2

        for a in split(subarray1):
            yield a
        for a in split(subarray2):
            yield a

for a in split(range(16)):
    print a

<强>输出

[0, 1, 2, 3, 4, 5, 6, 7]
[8, 9, 10, 11, 12, 13, 14, 15]
[0, 1, 2, 3]
[4, 5, 6, 7]
[0, 1]
[2, 3]
[0]
[1]
[2]
[3]
[4, 5]
[6, 7]
[4]
[5]
[6]
[7]
[8, 9, 10, 11]
[12, 13, 14, 15]
[8, 9]
[10, 11]
[8]
[9]
[10]
[11]
[12, 13]
[14, 15]
[12]
[13]
[14]
[15]

答案 3 :(得分:0)

def split(xs):
    n = len(xs)
    if n == 1:
        yield xs
    else:
        i = n / 2
        for xs_ in split(xs[:i]):
            yield xs_
        for _xs in split(xs[i:]):
            yield _xs

print list(split(range(10)))

以上是您的代码的更加懒惰的版本。但是,您仍然需要加载所有50 GB才能使len正常工作。

如果您知道所需的数据块大小,则可以避免将整个列表存储在内存中:

from itertools import islice
def sliceUp(xs_generator, chunk_size = 5):
    xs_generator = iter(xs_generator)
    while True:
        buf = islice(xs_generator, chunk_size)
        if buf == []:
            break
        yield buf