为什么Python的mmap不支持大文件?
[编辑:这个问题只适用于32位系统。如果你的电脑、操作系统和Python版本都是64位的,那么处理大文件时使用mmap是可靠且高效的。]
我正在写一个模块,其中有一部分功能是允许按位读取文件。因为这些文件可能很大(几百GB),所以我写了一个简单的类,让我可以像处理字符串一样处理文件,同时隐藏了所有的查找和读取操作。
在我写这个包装类的时候,我并不知道有一个叫做mmap模块。当我阅读mmap的文档时,我想:“太好了,这正是我需要的,我可以把我的代码拿掉,换成mmap。这样可能会更高效,而且删除代码总是好的。”
问题是,mmap在处理大文件时并不好用!这让我很惊讶,因为我觉得这应该是最明显的应用。如果文件超过几GB,我就会遇到一个EnvironmentError: [Errno 12] Cannot allocate memory
的错误。这只发生在32位的Python版本上,看来是因为地址空间不够,但我找不到相关的文档。
我的代码就是
f = open('somelargefile', 'rb')
map = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
所以我的问题是我是不是漏掉了什么明显的东西?有没有办法让mmap在大文件上也能正常工作,还是我应该回去用我之前的简单文件包装类?
更新:似乎大家觉得Python的mmap应该和POSIX的mmap有相同的限制。为了更好地表达我的沮丧,这里有一个简单的类,它实现了mmap的一小部分功能。
import os
class Mmap(object):
def __init__(self, f):
"""Initialise with a file object."""
self.source = f
def __getitem__(self, key):
try:
# A slice
self.source.seek(key.start, os.SEEK_SET)
return self.source.read(key.stop - key.start)
except AttributeError:
# single element
self.source.seek(key, os.SEEK_SET)
return self.source.read(1)
这个类是只读的,没有任何复杂的功能,但我可以像使用mmap一样这样做:
map2 = Mmap(f)
print map2[0:10]
print map2[10000000000:10000000010]
只是文件大小没有限制。其实并不难……
8 个回答
一个32位的程序和操作系统最多只能使用32位的内存,也就是4GB。还有其他因素会让这个总量变得更小;比如,Windows会为硬件访问保留0.5到2GB的内存,当然你的程序本身也会占用一些空间。
补充:你明显缺少的是对mmap这个机制的理解,不管在哪个操作系统上。mmap允许你把一个文件的一部分映射到内存中的某个范围内——一旦你完成了这个操作,访问这个文件的那部分就会变得非常高效。之所以高效,是因为映射只需要做一次,之后每次访问不同的范围时都不需要重新映射。缺点是你需要有一个足够大的地址范围来映射你想要的那部分。如果你想一次性映射整个文件,就需要在内存中有一个足够大的空隙来放下整个文件。如果没有这样的空隙,或者这个空隙比你的整个地址空间还要大,那就会失败。
抱歉我自己回答自己的问题,但我觉得我真正遇到的问题是没有意识到mmap其实是一个标准的POSIX系统调用,它有一些特定的特点和限制,而Python中的mmap只是用来展示这些功能。
Python的文档没有提到POSIX的mmap,所以如果你是一个对POSIX了解不多的Python程序员(就像我当时那样),那么你会觉得地址空间的问题看起来很随意,而且设计得很糟糕!
感谢其他发帖者教会我mmap的真正含义。不幸的是,没人给我推荐一个比我自己写的处理大文件为字符串的类更好的替代方案,所以我现在只能继续用它。也许等我有机会的时候,我会把它整理一下,作为我模块的公共接口的一部分。
根据IEEE 1003.1的定义:
mmap()函数的作用是建立一个进程的地址空间和一个文件、共享内存对象或某种类型的内存对象之间的映射。
它需要使用所有的虚拟地址空间,因为这正是mmap()
的功能。
即使实际上并不是在内存不足的情况下,这一点也无关紧要——你不能映射超过你可用的地址空间。因为你会把结果当作内存来访问,那么你打算如何访问文件中超过2^32字节的内容呢?即使mmap()
没有失败,你也只能在32位地址空间中读取前4GB的数据。一种方法是可以对文件进行滑动窗口的mmap()
操作,但这并不一定会带来好处,除非你能优化你的访问模式,减少访问之前窗口的次数。