【问题标题】:Why doesn't Python's mmap work with large files?为什么 Python 的 mmap 不能处理大文件?
【发布时间】:2010-12-12 07:55:36
【问题描述】:

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

我正在编写一个模块,其中允许对文件进行按位读取访问。这些文件可能很大(数百 GB),所以我编写了一个简单的类,让我可以将文件视为字符串并隐藏所有查找和读取。

在我编写包装类时,我不知道mmap module。在阅读 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]

除了对文件大小没有限制。真的不太难……

【问题讨论】:

  • 但是没有mmap的功能。 mmap 公开了一个缓冲区接口,您可以对其进行正则表达式匹配。 mmap 支持写入文件,mmap 支持共享内存。你的代码,甚至你的方法,都不会那样做。
  • 嗯,它具有 small 数量的 mmap 功能,但不受地址空间限制的影响。这只是一段玩​​具代码——我并不是说它是一个替代品!我认为这种方法模仿 mmap 的功能没有问题,尽管我可以理解它无法匹配性能。
  • 因为它不能实现mmap的功能。您将如何使用此实现 IPC,以便子进程可以通过共享内存块与父进程通信?此外,您的示例不是线程安全的,因为可能会发生不同线程中的两个 getitem 调用,以便第二个的查找在第一个查找之后立即发生,导致第一个的读取给出结果错误。
  • @dalke:好吧,我让步!正如我充分证明的那样,我对 POSIX mmap 了解不多。我只需要我可以相当简单地完成的功能子集(无线程等)。我会相信你的话:)

标签: python performance memory mmap


【解决方案1】:

您将长度参数设置为零,这意味着在整个文件中映射。在 32 位构建中,如果文件长度超过 2GB(可能是 4GB),则无法实现。

【讨论】:

  • 是的,我想映射整个文件。将其限制为几 GB 似乎是不合理的,尤其是当我需要只读访问权限时。 mmap 立即尝试保留 GB 的内存对我来说似乎很疯狂!
  • mmap'ing 不需要物理内存 - 它需要 虚拟地址空间 才能使文件可用。
  • @Andrew:那么我想我的问题是为什么它需要所有这些虚拟地址空间?没有它就很容易使文件表现得像一个字符串(特别是如果它是只读的)。也许我应该强调这是关于 Python mmap 模块的,它不必具有与 Unix mmap 系统调用相同的特性和限制。
  • 因为指向虚拟地址的指针仍然只有 32 位。 32 位 = 最多 4GB。 Python 使用本地架构的指针。
【解决方案2】:

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

编辑:您显然缺少对任何操作系统上 mmap 机制的理解。它允许您将文件的一部分映射到一定范围的内存——一旦您这样做了,对文件该部分的任何访问都会以尽可能少的开销发生。它的开销很低,因为映射只完成一次,并且每次访问不同的范围时都不必更改。缺点是您需要一个足以用于您要映射的部分的开放地址范围。如果您一次映射整个文件,则需要在内存映射中留一个足够大的孔以容纳整个文件。如果这样的漏洞不存在,或者比你的整个地址空间大,它就会失败。

【讨论】:

  • 是的,但 mmap 实际上并不需要 来处理所有这些内存 - 地址空间限制是一个实现细节。当然,如果我要求一个巨大的切片,那么可能会有内存问题,但没有必要保留内存。
  • "如果我要一个大切片" - 因为您使用 0 作为第二个参数,所以您的“切片”就是整个文件。
  • 是的,我要的是整个文件,但我不希望它被读入内存,除非我引用它的一部分。
  • 一个典型的 mmap 实现将保留您正在映射的对象的地址空间。如果无法进行该映射 - 例如由于没有足够的空间来映射请求的大小,mmap 将失败。在您访问它之前,mmap 实际上不会读取整个内容。但它会尝试创建地址空间映射。
【解决方案3】:

来自 IEEE 1003.1:

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

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

真的内存不足这一事实并不重要——你不能映射比你可用的更多的地址空间。既然您随后获取结果并像 内存一样进行访问,那么您究竟如何建议将超过 2^32 个字节访问到文件中?即使mmap() 没有失败,在 32 位地址空间中的空间用完之前,您仍然只能读取前 4GB。当然,您可以在文件上mmap() 滑动 32 位窗口,但这不一定会给您带来任何好处,除非您可以优化您的访问模式以限制您必须访问以前的窗口的次数。

【讨论】:

  • POSIX mmap 规范绝对相关。 Python mmap 模块的全部意义在于让您直接访问操作系统的 mmap,允许硬件指针访问文件数据,就好像它是内存一样。如果您想要更方便,请使用 Python 库或任何其他语言中的许多其他 IO 相关模块。否则,您需要忍受底层操作系统和 CPU 虚拟内存架构的限制。
  • Windows 实现 POSIX api 调用。 POSIX mmap 在 Windows 上的作用与在 Linux 上的作用相同:它将文件映射到虚拟地址空间。
  • 如果您还没有,请阅读en.wikipedia.org/wiki/Mmap 并注意有关 Windows MapViewOfFile 的说明;查看 python Modules/mmapmodule.c 的代码,这就是它在 Windows 上使用的代码。顺便说一句,bugs.python.org 总是欢迎改进 Python 文档的建议。
  • 在 windows 上,python 将 mmap 包装在 MapViewOfFile win32 调用之上,其操作与 *nix mmap 非常相似。该文档有一些关于 unix/windows 之间关于 mmap 的差异的注释。 mmap 是 Python 的“可选操作系统服务”的一部分,其重点是包装常见的操作系统功能,因此受到底层操作系统的限制。
  • 谢谢大家,我想大部分问题是 Python 文档不够明确。
【解决方案4】:

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

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

这很好,但它不是内存映射,它以更严格的 api 为代价提供了更广泛数据范围的优势。

【讨论】:

    【解决方案5】:

    您要求操作系统将整个文件映射到内存范围内。直到你通过读/写触发页面错误才会被读取,但它仍然需要确保整个范围对你的进程可用,如果这个范围太大,就会有困难。

    【讨论】:

      【解决方案6】:

      mmap 模块提供了您在大文件中浏览所需的所有工具,但由于其他人提到的限制,您无法一次映射它。您可以一次映射一个大小合适的块,进行一些处理,然后取消映射并映射另一个。 mmap 类的关键参数是 lengthoffset,它们的功能与听起来完全一样,允许您映射 length 字节,从映射文件中的字节 offset 开始。任何时候你想读取映射窗口之外的一段内存,你必须在一个新窗口中映射。

      【讨论】:

        【解决方案7】:

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

        Python 文档没有提到 POSIX mmap,因此,如果您是作为一名 Python 程序员而对 POSIX 了解不多(正如我所做的那样),那么地址空间问题就显得相当随意且设计不当!

        感谢其他发帖者教我mmap的真正含义。不幸的是,没有人提出更好的替代我手工制作的类的方法来将大文件视为字符串,所以我现在必须坚持使用它。或许我会在有机会时将其清理并使其成为我模块的公共接口的一部分。

        【讨论】:

        • 在我看来,您的手工课程非常适合您的需求。不会因为它们是环境的一部分而强迫使用不合适的机制。感谢分享学习经验。你让我免于重新发明同样的问题。
        【解决方案8】:

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

        mmap() 需要 CPU 硬件支持才能处理大于几 GiB 的大文件。

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

        MMU 是硬件,每当访问与不在物理 RAM 中的数据相对应的地址时,它都会产生中断,并且操作系统将以在运行时有意义的方式处理中断,因此访问代码永远不知道(或需要要知道)数据不适合 RAM。

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

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

        【讨论】:

          猜你喜欢
          • 2012-10-24
          • 1970-01-01
          • 2013-05-15
          • 2017-12-24
          • 1970-01-01
          • 1970-01-01
          • 2023-03-11
          • 2011-06-04
          • 1970-01-01
          相关资源
          最近更新 更多