延迟评估迭代NumPy数组

时间:2010-08-03 00:50:47

标签: python memory-management numpy lazy-evaluation

我有一个Python程序来处理相当大的NumPy数组(数百兆字节),这些数组存储在pickle文件的磁盘上(每个文件一个~100MB数组)。当我想对数据运行查询时,我通过pickle加载整个数组,然后执行查询(这样从Python程序的角度来看整个数组都在内存中,即使操作系统正在交换它) 。我这样做主要是因为我相信能够在NumPy数组上使用向量化操作比在每个项目中使用for循环要快得多。

我在一台Web服务器上运行它,它有一个内存限制,我很快就遇到了。我在数据上运行了许多不同类型的查询,因此编写“分块”代码,从单独的pickle文件加载部分数据,处理它们,然后进入下一个块可能会增加很多复杂性。对于处理这些大型数组的任何函数来说,最好使这个“分块”透明。

似乎理想的解决方案就像生成器一样,它会定期从磁盘加载一块数据,然后逐个传递数组值。这将大大减少程序所需的内存量,而不需要对各个查询功能进行任何额外的工作。有可能做这样的事吗?

3 个答案:

答案 0 :(得分:9)

PyTables是用于管理分层数据集的包。它旨在为您解决这个问题。

答案 1 :(得分:4)

NumPy的内存映射数据结构 memmap )可能是个不错的选择。

您可以从磁盘上的二进制文件访问NumPy阵列,而无需立即将整个文件加载到内存中。

(注意,我相信,但我不确定,Numpys memmap对象与Pythons相同 - 特别是,NumPys类似于数组,Python类似于文件。)

方法签名是:

A = NP.memmap(filename, dtype, mode, shape, order='C')

所有参数都很简单(即它们具有与NumPy中其他地方相同的含义),除了'order',它指的是ndarray内存布局的顺序。我相信默认值是'C',而(唯一的)其他选项是'F',对于Fortran - 和其他地方一样,这两个选项分别代表行主要和列主要顺序。

这两种方法是:

flush (将您对数组所做的任何更改写入磁盘);和

关闭(将数据写入memmap数组,或者更确切地说,将数据写入类似于数组的内存映射到存储在磁盘上的数据)

使用示例:

import numpy as NP
from tempfile import mkdtemp
import os.path as PH

my_data = NP.random.randint(10, 100, 10000).reshape(1000, 10)
my_data = NP.array(my_data, dtype="float")

fname = PH.join(mkdtemp(), 'tempfile.dat')

mm_obj = NP.memmap(fname, dtype="float32", mode="w+", shape=1000, 10)

# now write the data to the memmap array:
mm_obj[:] = data[:]

# reload the memmap:
mm_obj = NP.memmap(fname, dtype="float32", mode="r", shape=(1000, 10))

# verify that it's there!:
print(mm_obj[:20,:])

答案 2 :(得分:2)

  

这似乎是理想的解决方案   像发电机那样的东西   定期装一块   来自磁盘的数据然后通过了   数组值逐个输出。这个   会大幅减少金额   程序所需的内存   无需任何额外的工作   单个查询的一部分   功能。有可能吗?   这样的事情?

是的,但不是将数组保存在单个pickle中的磁盘上 - pickle协议不是为“增量反序列化”而设计的。

可以将多个泡菜写入同一个打开的文件,一个接一个(使用dump dumps),然后“懒惰的迭代评估器”每次只需要使用pickle.load

示例代码(Python 3.1 - 在2.当然你需要cPickle而不是pickle-1协议等等; - ):

>>> import pickle
>>> lol = [range(i) for i in range(5)]
>>> fp = open('/tmp/bah.dat', 'wb')
>>> for subl in lol: pickle.dump(subl, fp)
... 
>>> fp.close()
>>> fp = open('/tmp/bah.dat', 'rb')
>>> def lazy(fp):
...   while True:
...     try: yield pickle.load(fp)
...     except EOFError: break
... 
>>> list(lazy(fp))
[range(0, 0), range(0, 1), range(0, 2), range(0, 3), range(0, 4)]
>>> fp.close()