我需要处理一些比RAM大几百倍的数据。我想在大块中读取,处理它,保存结果,释放内存并重复。有没有办法在python中提高效率?
答案 0 :(得分:23)
一般关键是你要迭代地处理文件。
如果您只是处理文本文件,这很简单:for line in f:
一次只能读取一行。 (实际上它缓冲了一些东西,但缓冲区很小,你不必担心它。)
如果您正在处理其他特定文件类型,例如numpy二进制文件,CSV文件,XML文档等,通常会有类似的专用解决方案,但没有人可以向您描述它们,除非您告诉我们你有什么样的数据。
但是如果你有一般的二进制文件怎么办?
首先,read
方法需要一个可选的最大字节来读取。所以,而不是:
data = f.read()
process(data)
你可以这样做:
while True:
data = f.read(8192)
if not data:
break
process(data)
你可能想要写一个这样的函数:
def chunks(f):
while True:
data = f.read(8192)
if not data:
break
yield data
然后你可以这样做:
for chunk in chunks(f):
process(chunk)
您也可以使用双参数iter
执行此操作,但很多人发现有点模糊:
for chunk in iter(partial(f.read, 8192), b''):
process(chunk)
无论哪种方式,此选项都适用于下面的所有其他变体(单个mmap
除外,这对于没有任何意义而言是微不足道的。)
那里的数字8192没有什么神奇之处。您通常需要2的幂,理想情况下是系统页面大小的倍数。除此之外,无论您使用的是4KB还是4MB,您的性能都不会有太大差异 - 如果确实如此,您将必须测试哪种方法最适合您的用例。
无论如何,这假设您可以一次处理每个8K而不保留任何上下文。如果你是,例如,将数据输入渐进式解码器或者其他东西,那就完美了。
但是如果你需要一次处理一个“块”,你的块可能最终跨越8K边界。你是如何处理的?
这取决于你的文件在文件中的分隔方式,但基本思路非常简单。例如,假设您使用NUL字节作为分隔符(不太可能,但很容易显示为玩具示例)。
data = b''
while True:
buf = f.read(8192)
if not buf:
process(data)
break
data += buf
chunks = data.split(b'\0')
for chunk in chunks[:-1]:
process(chunk)
data = chunks[-1]
这种代码在网络中很常见(因为sockets
不能只是“全部读取”,所以总是必须读入缓冲区和块进入消息),因此您可以在使用类似于文件格式的协议的网络代码中找到一些有用的示例。
或者,您可以使用mmap
。
如果您的虚拟内存大小大于文件,则这很简单:
with mmap.mmap(f.fileno(), access=mmap.ACCESS_READ) as m:
process(m)
现在m
就像一个巨大的bytes
对象,就像你调用read()
将整个内容读入内存一样 - 但操作系统会自动将位内容读入和输出必要的记忆。
如果你试图读取的文件太大而不适合你的虚拟内存大小(例如,带有32位Python的4GB文件,或带有64位Python的20EB文件 - 这种情况很可能只发生在2013,如果您正在读取稀疏或虚拟文件,例如Linux上另一个进程的VM文件,则必须一次在文件的一部分中实现windowing-mmap。例如:
windowsize = 8*1024*1024
size = os.fstat(f.fileno()).st_size
for start in range(0, size, window size):
with mmap.mmap(f.fileno(), access=mmap.ACCESS_READ,
length=windowsize, offset=start) as m:
process(m)
当然,如果你需要分割内容,映射窗口与读取块有相同的问题,你可以用同样的方法解决它。
但是,作为优化,您可以将窗口向前滑动到包含最后一条完整邮件末尾的页面,而不是一次缓冲,而不是缓冲,然后您可以避免任何复制。这有点复杂,所以如果你想这样做,请搜索“滑动mmap窗口”之类的内容,如果你遇到问题就写一个新问题。