为什么Python的mmap不能处理大文件?
[编辑:这个问题只适用于32位系统。 如果你的计算机,你的操作系统和你的python实现是64位的,那么mmap-large文件的工作是可靠的,效率非常高。
我正在写一个模块,其中包括允许按位读取访问文件。 这些文件可能很大(数百GB),所以我写了一个简单的类,让我像string一样对待文件,并隐藏所有的查找和读取。
当时我写封装类,我不知道mmap模块 。 在阅读mmap的文档时,我认为“很好 – 这正是我所需要的,我将取出我的代码,并用mmapreplace它,这可能更有效,删除代码总是好的。
问题是,mmap不适用于大文件! 这对我来说是非常惊人的,因为我认为这也许是最明显的应用。 如果该文件高于几千兆字节,那么我得到一个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具有相同的限制。 为了更好地expression我的挫折感,这里有一个简单的类,它只有mmap的一小部分function。
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]
除了文件大小没有限制。 真的不太难
从IEEE 1003.1:
mmap()函数应该build立进程的地址空间和文件,共享内存对象或[TYM]types的内存对象之间的映射。
它需要所有的虚拟地址空间,因为这正是mmap()
所做的 。
它并不是真的内存不足并不重要 – 你不能映射更多的地址空间。 既然你然后把结果和访问,如果它是内存,你怎么build议访问超过2 ^ 32字节的文件? 即使mmap()
没有失败,在32位地址空间内用完空间之前,仍然只能读取前4GB。 当然,您可以在文件上mmap()
一个滑动的32位窗口,但是除非您可以优化访问模式,以便限制访问以前的窗口的次数,否则不一定会为您带来任何好处。
抱歉回答我自己的问题,但我认为真正的问题是我没有意识到,mmap是一个标准的POSIX系统调用具有特定的特征和限制,而且Python mmap只是为了公开其function。
Python的文档没有提到POSIX mmap,所以如果你是作为一个没有太多POSIX知识的Python程序员来做的话(就像我做的那样),那么地址空间问题就显得相当随心所欲,devise的很糟糕!
感谢其他海报,教导我mmap的真正意义。 不幸的是,没有人提出一个更好的替代scheme来处理大文件作为string,所以现在我必须坚持下去。 也许我会清理它,并在我有机会的时候将其作为我模块的公共接口的一部分。
32位程序和操作系统只能寻址最多32位内存,即4GB。 还有其他因素使总数更小; 例如,Windows保留0.5到2GB的硬件访问权限,当然你的程序也要占用一些空间。
编辑:你所缺less的显而易见的东西是在任何操作系统上理解mmap的机制。 它允许你将一个文件的一部分映射到一个内存区域 – 一旦你完成了这个任务,任何对这个文件部分的访问都会以最小的开销进行。 它的开销很低,因为映射只执行一次,并且每次访问不同的范围时都不必更改。 缺点是你需要一个开放的地址范围足够你想要映射的部分。 如果一次映射整个文件,则需要足够大的内存映射以适应整个文件。 如果这样的漏洞不存在,或者比你的整个地址空间大,那就失败了。
mmap模块提供了您需要在大文件中查找的所有工具,但是由于其他人员提到的限制,您无法一次将其全部映射。 您可以一次映射一个好的大小块,做一些处理,然后取消映射,并映射另一个。 mmap
类的关键参数是length
和offset
,它们的确如此,允许您映射length
字节,从映射文件中的字节offset
开始。 任何时候你想读取映射窗口以外的内存部分,都必须在新窗口中映射。
你缺less的一点是,mmap是一个内存映射函数,它将文件映射到内存中,以任何方式在请求的数据范围内进行任意访问。
你正在寻找什么听起来更像是某种types的数据窗口类,它提供了一个API,允许你随时查看大型数据结构的小窗口。 除了通过调用数据窗口自己的API之外,不可能访问这个窗口的界限。
这很好,但它不是一个内存映射,它提供了一个更广泛的数据范围的优势,代价是更严格的api。
您将长度参数设置为零,这意味着在整个文件中映射。 在32位版本上,如果文件长度大于2GB(可能是4GB),则不可能。
使用64位计算机,64位操作系统和64位Python实现,或避免使用memmap()
memmap()
需要 CPU硬件支持才能使大于几个GiB的大文件变得有意义。
它使用CPU的MMU和中断子系统来允许暴露数据,就好像它已经加载了RAM。
MMU是一种硬件,只要与不在物理RAM中的数据相对应的地址被访问,就会产生中断,操作系统将以运行时有意义的方式处理中断,所以访问代码永远不会知道(或需要知道)数据不适合RAM。
这使得您的访问代码很容易编写。 但是,以这种方式使用memmap()
,涉及的每件事都需要处理64位地址。
否则,最好避免使用memmap()
并进行自己的内存pipe理。
您要求操作系统将整个文件映射到内存范围内。 直到通过读写触发页面错误才会被读取,但是仍然需要确保整个范围对于您的过程是可用的,并且如果该范围太大将会有困难。