python递归生成以减少内存占用

2024-06-28 11:13:18 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个类似下面的函数,它递归地将一个大数组分成两个子数组,并收集所有子数组以备将来处理。我的问题是,是否有办法在拆分过程中产生子数组以减少内存占用,例如,split调用的数组很大,~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

Tags: 函数内存return过程defwithrange数组
3条回答

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

例如:

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

你真正想做的是把原始数据分块读入。在

你可以试着用一个memoryview,伊莱本德斯基写了一个很好的blog entry。在

不过,我会尽力总结一下。在对象上创建memoryview时,您正在创建对存储对象的内存中的(ctype)数据结构的引用。memoryview切片是在该数据结构中查找某些值的引用。无需在同一结构上复制多个基础视图即可。这就像对列表或数组进行切片一样。在

但是,您的数据必须支持缓冲区协议(numpy数组和bytearray可以做到这一点,但是列表不支持)。在

我想加上这一行就足够了

memview = memoryview(yourarray)

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

不过,请注意两件事:

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

示例:

^{pr2}$

当然,所有这些都是假设您可以在一个时间点将50GB加载到内存中。如果您不能这样做,您应该看看mmap模块。在

编辑-字符串的numpy数组

Will memoryview work with a numpy array of strings?

seems not. e.g. memview = memoryview(np.array(["abcde", 'aa'])), memview[0] is 'abcde', but memview[1] is 'aa\x00\x00\x00'

好吧,从技术上讲,它确实有效。它只是展示了numpy如何存储字符串数组。那就是:糟糕;)

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

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

您可以看到dtype是|S5,表示长度为5的字符串。较短字符串的“missing”位置用空(零)字节(\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数组。在

我不知道你想达到什么目的。是的,您可以使用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

输出

^{pr2}$

相关问题 更多 >