47

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

我正在编写一个模块,其中允许对文件进行按位读取访问。这些文件可能很大(数百 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]

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

4

8 回答 8

38

来自 IEEE 1003.1:

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

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

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

于 2009-11-02T16:52:46.003 回答
18

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

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

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

于 2009-11-03T11:04:31.337 回答
17

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

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

于 2009-11-02T16:01:05.093 回答
9

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

于 2009-11-02T20:48:42.750 回答
6

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

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

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

于 2009-11-02T17:34:48.507 回答
4

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

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

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

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

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

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

于 2016-12-13T04:35:15.613 回答
2

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

于 2009-11-02T15:42:06.903 回答
1

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

于 2009-11-02T17:40:28.903 回答