为什么Python的mmap不适用于大文件?

时间:2009-11-02 15:34:22

标签: python performance memory mmap

[编辑:此问题仅适用于32位系统。如果你的计算机,你的操作系统和你的python实现是64位的,那么mmap-ing巨大的文件可以正常工作并且非常有效。]

我正在编写一个模块,其中包括允许按位读取文件访问权限。这些文件可能很大(数百GB),所以我编写了一个简单的类,让我像处理字符串一样处理文件并隐藏所有的搜索和阅读。

当我写我的包装类时,我不知道mmap module。在阅读mmap的文档时,我认为“很棒 - 这正是我需要的,我将取出代码并用mmap替换它。它可能效率更高,删除代码总是好的。” / em>的

问题是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具有相同的限制。为了更好地表达我的挫败感,这是一个简单的类,它具有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 个答案:

答案 0 :(得分:37)

来自IEEE 1003.1:

  

mmap()函数应该建立一个   进程'地址之间的映射   空间和文件,共享内存   对象或[TYM]键入的内存   对象

它需要所有虚拟地址空间,因为这正是mmap() 所做的

实际上内存不足的事实并不重要 - 您无法映射比可用地址空间更多的地址空间。既然您将结果和访问视为内存,那么您究竟打算如何访问文件中超过2 ^ 32个字节?即使mmap()没有失败,您仍然只能在32位地址空间中的空间不足之前读取前4GB。当然,您可以mmap()在文件上滑动32位窗口,但除非您可以优化访问模式以限制您访问之前的次数,否则这不一定能为您带来任何好处。窗户。

答案 1 :(得分:17)

很抱歉回答我自己的问题,但我认为我遇到的真正问题是没有意识到mmap是一个标准的POSIX系统调用,具有特定的特征和限制,并且Python mmap应该只是为了公开它的功能。

Python文档没有提到POSIX mmap,所以如果你作为一个Python程序员而不太了解POSIX(正如我所做的那样),那么地址空间问题就显得非常随意且设计糟糕了!

感谢其他海报教我mmap的真正含义。不幸的是,没有人建议我将手工制作的类更好地替换为将大文件作为字符串处理,所以我现在必须坚持使用它。也许我会清理它,并在我有机会时将其作为我模块的公共界面的一部分。

答案 2 :(得分:16)

32位程序和操作系统最多只能处理32位内存,即4GB。还有其他因素使总数更小;例如,Windows为硬件访问保留0.5到2GB之间,当然你的程序也会占用一些空间。

编辑:您缺少的显而易见的事情是了解mmap在任何操作系统上的机制。它允许您将文件的一部分映射到一系列内存 - 一旦完成,就可以以尽可能少的开销访问文件的该部分。它的开销很低,因为映射只执行一次,并且每次访问不同的范围时都不必更改。缺点是您需要一个开放的地址范围,足以容纳您尝试映射的部分。如果您要一次映射整个文件,则需要在内存映射中有一个大小足以适合整个文件的漏洞。如果这样的洞不存在,或者比整个地址空间大,那就失败了。

答案 3 :(得分:9)

mmap模块提供了在大文件中浏览所需的所有工具,但由于其他人提到的限制,您无法一次性映射 < / strong>即可。您可以一次映射一个大小合适的块,进行一些处理,然后取消映射并映射另一个。 mmap类的关键参数是lengthoffset,它们完全符合它们的含义,允许您映射length个字节,从字节{{1}开始在映射文件中。只要您希望读取映射窗口之外的内存部分,就必须在新窗口中进行映射。

答案 4 :(得分:6)

您缺少的一点是mmap是一种内存映射函数,它将文件映射到内存中,以便通过任何方式在请求的数据范围内进行任意访问。

您正在寻找的内容听起来更像某种数据窗口类,它提供了一个api,允许您随时查看大型数据结构的小窗口。除了通过调用数据窗口自己的api之外,访问超出此窗口范围是不可能的。

这很好,但它不是内存映射,它提供了更广泛的数据范围的优势,而代价是更具限制性的api。

答案 5 :(得分:2)

您将length参数设置为零,这意味着映射整个文件。在32位版本上,如果文件长度超过2GB(可能是4GB),则无法进行此操作。

答案 6 :(得分:2)

使用64位计算机,64位操作系统和64位python实现,或避免memmap()

memmap() 需要 CPU硬件支持才能使用大于几个GiB的大文件。

它使用CPU的MMU和中断子系统来允许公开数据,就像它已经加载了ram一样。

MMU是硬件,只要访问与物理RAM中不存在的数据相对应的地址就会生成中断,并且OS将以在运行时有意义的方式处理中断,因此访问代码永远不会知道(或需要要知道数据不适合RAM。

这使您的访问代码易于编写。但是,要以这种方式使用memmap(),所涉及的所有内容都需要处理64位地址。

否则最好完全避免memmap()并进行自己的内存管理。

答案 7 :(得分:1)

您要求操作系统将整个文件映射到内存范围。在您通过读/写触发页面错误之前不会读取它,但它仍然需要确保整个范围可用于您的过程,如果该范围太大,则会有困难。