我有一个类似下面的函数,递归地将一个大数组拆分成两个子数组,并收集所有这些数据以供将来处理。我的问题是,如果有一种方法可以在分裂过程中产生子阵列以减少内存占用,例如,分割调用的数组很大,~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
答案 0 :(得分:2)
您可以尝试使用memoryview,Eli Bendersky已经为此写了一个不错的blog entry。
我会尽力总结一下。在对象上创建内存视图时,您正在创建对存储对象的内存中的(ctype)数据结构的引用。 memoryview slice是在此数据结构中查找某些值的参考。您可以在同一个底层结构上创建多个视图,而无需复制任何内容。这就像切片列表或数组一样。
你的数据必须支持缓冲协议(numpy数组和bytearrays这样做,但是列表没有)。
我认为添加这一行就足够了
memview = memoryview(yourarray)
代码并将其传递给split而不是数组。
请注意两件事:
示例:
>>> 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模块。
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