使用内存映射文件读取大文件

Posted

技术标签:

【中文标题】使用内存映射文件读取大文件【英文标题】:Reading huge files using Memory Mapped Files 【发布时间】:2012-03-25 10:07:33 【问题描述】:

我看到很多文章建议不要将大文件映射为 mmap 文件,这样虚拟地址空间就不会被 mmap 单独占用。

在地址空间急剧增加的 64 位进程中,这种情况有何变化? 如果我需要随机访问一个文件,是否有理由不一次映射整个文件? (几十GB的文件)

【问题讨论】:

【参考方案1】:

需要注意的一点是,在创建映射时,内存映射需要大块连续的(虚拟)内存;在 32 位系统上,这特别糟糕,因为在已加载的系统上,不太可能长时间运行连续 ram,并且映射将失败。在 64 位系统上,这要容易得多,因为 64 位的上限...很大。

如果您在受控环境中运行代码(例如,您正在构建自己的 64 位服务器环境并且知道可以正常运行此代码),请继续映射整个文件并处理它。

如果您尝试编写可在任何类型的配置上运行的软件中的通用代码,您将需要坚持使用较小的分块映射策略。例如,将大文件映射到 1GB 块的集合,并具有一个抽象层,该层接受诸如 read(offset) 之类的操作,并在执行操作之前将它们转换为正确块中的偏移量。

希望对您有所帮助。

【讨论】:

【参考方案2】:

有理由仔细考虑使用内存映射文件,即使在 64 位平台上(虚拟地址空间大小不是问题)也是如此。它与(潜在的)错误处理有关。

“按常规”读取文件时 - 任何 I/O 错误都会由相应的函数返回值报告。其余的错误处理由您决定。

OTOH 如果在隐式 I/O 期间出现错误(由于页面错误并尝试将所需的文件部分加载到适当的内存页面中) - 错误处理机制取决于操作系统。

在 Windows 中,错误处理是通过 SEH 执行的 - 即所谓的“结构化异常处理”。异常传播到用户模式(应用程序的代码),您有机会正确处理它。正确的处理要求您在编译器中使用适当的异常处理设置进行编译(以保证调用析构函数,如果适用)。

但我不知道在 unix/linux 中如何执行错误处理。

附:我不是说不要使用内存映射文件。我说小心点

【讨论】:

@David Heffernan:不完全是,这取决于你在读什么。如果加载程序代码或数据(全局、堆栈/tls 或堆)时出错 - 进程将被终止。操作系统不给应用程序处理此问题的机会,因为应用程序已经“损坏”。由应用程序自己创建的内存映射文件引起的 OTOH 错误 - 有更多机会正确处理 所以你是说内存映射文件的错误不同于读取哑指针?无论如何,我看不到您对问题的回答的相关性。即使这是合理的建议,它也与所提出的问题正交。 @David Heffernan:当然。操作系统不知道您“读取了一个哑指针”。从它的角度来看,您尝试取消引用不可访问的虚拟地址,它会引发异常,并且您的应用程序有机会处理它。是错误还是合法条件 - 取决于应用程序。我同意它与“一次或分段映射整个文件”的问题正交。我认为问题是映射与其他替代方案 问题是映射整个文件与映射小块【参考方案3】:

在 64 位上,继续映射文件。

有一点需要考虑,基于 Linux 经验:如果访问是真正随机的,并且文件比您期望在 RAM 中缓存的要大得多(因此再次访问页面的机会很小),那么它是值得的将MADV_RANDOM 指定为madvise 以阻止命中文件页面的不断积累,并且毫无意义地交换其他实际有用的内容。不过不知道windows equivalent API 是什么。

【讨论】:

以上是关于使用内存映射文件读取大文件的主要内容,如果未能解决你的问题,请参考以下文章

C#大文件读取和查询--内存映射

C#大文件读取和查询--内存映射

(整理二)读取大日志文件

内存映射文件的性能特点

np.memmap读取大文件

读取内存内存映射文件 C++ 和 C#