【问题标题】:mmap() vs. reading blocksmmap() 与读取块
【发布时间】:2010-09-07 22:34:25
【问题描述】:

我正在开发一个程序,该程序将处理大小可能为 100GB 或更大的文件。这些文件包含可变长度记录集。我已经启动并运行了第一个实现,现在正在寻求提高性能,尤其是在更有效地执行 I/O 方面,因为输入文件被扫描了很多次。

使用mmap() 与通过C++ 的fstream 库读取块是否有经验法则?我想做的是将大块从磁盘读取到缓冲区中,从缓冲区中处理完整的记录,然后读取更多。

mmap() 代码可能会变得非常混乱,因为mmap'd 块需要位于页面大小的边界(我的理解)并且记录可能跨越页面边界。使用fstreams,我可以找到记录的开头并再次开始阅读,因为我们不限于阅读位于页面大小边界上的块。

如果不先编写完整的实现,我如何在这两个选项之间做出决定?任何经验法则(例如,mmap() 快 2 倍)或简单测试?

【问题讨论】:

  • 这是一个有趣的阅读:medium.com/@sasha_f/… 在实验中mmap() 比使用系统调用快 2-6 倍,例如read().

标签: c++ file-io fstream mmap


【解决方案1】:

我试图找到关于 Linux 上 mmap / 读取性能的最终决定,我在 Linux 内核邮件列表上发现了一篇不错的帖子 (link)。它是从 2000 年开始的,因此从那时起内核中的 IO 和虚拟内存有了很多改进,但它很好地解释了 mmap 或 read 可能更快或更慢的原因。

  • 对mmap 的调用比read 的开销更大(就像epoll 的开销比poll 的开销更大,poll 的开销比read 的开销更大)。更改虚拟内存映射在某些处理器上是一项相当昂贵的操作,原因与在不同进程之间切换的成本非常高。
  • IO 系统已经可以使用磁盘缓存,所以如果你读取一个文件,无论你使用什么方法,你都会命中或错过缓存。

然而,

  • 内存映射对于随机访问通常更快,尤其是在您的访问模式稀疏且不可预测的情况下。
  • 内存映射允许您继续使用缓存中的页面,直到完成。这意味着如果您长时间大量使用文件,然后将其关闭并重新打开,页面仍然会被缓存。使用read,您的文件可能在很久以前就已从缓存中刷新。如果您使用文件并立即丢弃它,则不适用。 (如果您尝试 mlock 页面只是为了将它们保留在缓存中,那么您就是在试图智取磁盘缓存,而这种愚蠢的做法很少有助于系统性能。
  • 直接读取文件非常简单快捷。

mmap/read 的讨论让我想起了另外两个关于性能的讨论:

  • 一些 Java 程序员震惊地发现非阻塞 I/O 通常比阻塞 I/O 慢,如果您知道非阻塞 I/O 需要进行更多的系统调用,这非常有意义。

    李>
  • 其他一些网络程序员惊讶地发现epoll 通常比poll 慢,如果您知道管理epoll 需要进行更多的系统调用,这非常有意义。

结论:如果您随机访问数据、将其保存很长时间,或者如果您知道可以与其他进程共享,请使用内存映射(MAP_SHARED 不是很有趣,如果没有实际的共享)。如果您按顺序访问数据或在读取后将其丢弃,则可以正常读取文件。如果任何一种方法都可以让你的程序变得不那么复杂,那么那个。对于许多现实世界的案例,如果不测试您的实际应用程序而不是基准测试,就无法确定一种方法会更快。

(抱歉,这个问题被删除了,但我一直在寻找答案,而这个问题一直出现在 Google 结果的顶部。)

【讨论】:

  • 请记住,使用任何基于 2000 年代硬件和软件的建议,而今天不对其进行测试将是一种非常可疑的方法。此外,虽然该线程中有关mmap 与read() 的许多事实仍然像过去一样真实,但整体性能并不能真正通过将利弊相加来确定,而只能通过测试来确定特定的硬件配置。例如,“对 mmap 的调用比读取的开销更大”是有争议的 - 是的 mmap 必须将映射添加到进程页表,但 read 必须将所有读取字节从内核复制到用户空间。
  • 结果是,在我的(现代英特尔,大约 2018 年)硬件上,mmap 对于大于页面大小 (4 KiB) 的读取的开销低于 read。现在确实,如果您想以稀疏和随机的方式访问数据,mmap 确实非常好 - 但反过来不一定正确:mmap 可能仍然是顺序访问的最佳选择。
  • @BeeOnRope:您可能对基于 2000 年代硬件和软件的建议持怀疑态度,但我对不提供方法和数据的基准更加持怀疑态度。如果您想证明mmap 更快,我希望至少能看到带有表格结果的整个测试设备(源代码)以及处理器型号。
  • 我并不是要声称人们应该接受我的结果,而不是链接线程中提供的结果。我的意思是两者都不够:人们应该在自己的系统上对其进行测试,而不是简单地接受来自该线程的结果(该线程也没有提供详细的方法或数据)。我提供我的结果主要是为了指出我今天的结果与保罗当时的结果相反,作为呼吁人们在当地进行测试的一部分。非定量论据只有在一个解决方案支配另一个解决方案时才真正令人信服,而这里的情况并非如此。
  • @DietrichEpp - 是的,我会精通 TLB 效果。请注意,mmap 不会刷新 TLB,除非在异常情况下(但 munmap 可能)。我的测试包括两个微基准测试(包括munmap)和还包括在实际用例中运行的“应用程序中”。当然我的应用程序和你的应用程序不一样,所以人们应该在本地测试。甚至不清楚mmap 是否受到微基准的青睐:read() 也得到了很大的提升,因为用户端目标缓冲区通常停留在 L1 中,这在更大的应用程序中可能不会发生。所以,是的,“这很复杂”。
【解决方案2】:

这里已经有很多很好的答案,涵盖了许多要点,所以我只添加几个我没有看到上面直接解决的问题。也就是说,这个答案不应该被认为是利弊的综合,而应该是对这里其他答案的补充。

mmap 看起来很神奇

以文件已经完全缓存的情况1作为基线2,mmap 可能看起来很像 magic:

  1. mmap 只需要 1 次系统调用来(可能)映射整个文件,之后不再需要系统调用。
  2. mmap 不需要将文件数据从内核复制到用户空间。
  3. mmap 允许您“作为内存”访问文件,包括使用您可以对内存执行的任何高级技巧对其进行处理,例如编译器自动矢量化、SIMD 内在函数、预取、优化的内存解析例程、OpenMP、等

在文件已经在缓存中的情况下,似乎无法击败:你只是直接访问内核页面缓存作为内存,它不会比这更快。

嗯,可以的。

mmap 实际上并不神奇,因为...

mmap 仍然可以按页面工作

mmap 与 read(2) 的主要隐藏成本(这实际上是 读取块 的可比操作系统级系统调用)是使用 mmap 你需要做“一些为在新映射中访问的每个 4K 页面工作”,即使它可能被页面错误机制隐藏。

举个例子,一个典型的实现只需要mmaps 整个文件将需要故障输入,因此 100 GB / 4K = 2500 万次故障才能读取 100 GB 文件。现在,这些将是minor faults,但是 2500 万个页面错误仍然不会很快。在最好的情况下,一个小故障的成本可能在 100 纳米。

mmap 严重依赖 TLB 性能

现在,您可以将MAP_POPULATE 传递给mmap 告诉它在返回之前设置所有页表,因此在访问它时应该不会出现页面错误。现在,这有一个小问题,它还将整个文件读入 RAM,如果您尝试映射 100GB 文件,这将会爆炸 - 但现在让我们忽略它3。内核需要执行每页工作来设置这些页表(显示为内核时间)。这最终成为mmap 方法中的主要成本,并且与文件大小成正比(即,随着文件大小的增长,它的重要性不会相对降低)4。

最后,即使在用户空间访问这样的映射也不是完全免费的(与不是源自基于文件的mmap 的大内存缓冲区相比)——即使设置了页表,每次访问从概念上讲,新页面将导致 TLB 未命中。由于mmap文件意味着使用页面缓存及其 4K 页面,因此对于一个 100GB 的文件,您再次产生了 2500 万倍的成本。

现在,这些 TLB 未命中的实际成本在很大程度上取决于至少以下硬件方面:(a) 你有多少 4K TLB 实体以及其余的转换缓存工作如何执行 (b) 硬件的性能如何prefetch 处理 TLB - 例如,prefetch 可以触发页面遍历吗? (c) 页面遍历硬件的速度和并行度。在现代高端 x86 Intel 处理器上,page walk 硬件通常非常强大:至少有 2 个并行 page walker,page walk 可以与继续执行同时发生,并且硬件预取可以触发 page walk。因此,TLB 对 流式传输 读取负载的影响相当低 - 无论页面大小如何,这种负载通常都会执行类似的操作。但是,其他硬件通常要差得多!

read() 避免了这些陷阱

read() 系统调用,通常是在 C、C++ 和其他语言中提供的“块读取”类型调用的基础,它有一个每个人都清楚的主要缺点:

  • 每个 N 字节的 read() 调用必须将 N 字节从内核复制到用户空间。

另一方面,它避免了上述大部分成本 - 您无需将 2500 万个 4K 页面映射到用户空间。您通常可以malloc 用户空间中的单个缓冲区小缓冲区,并在所有read 调用中重复使用它。在内核方面,4K 页面或 TLB 未命中几乎没有问题,因为所有 RAM 通常使用几个非常大的页面(例如,x86 上的 1 GB 页面)进行线性映射,因此页面缓存中的底层页面被覆盖在内核空间中非常有效。

所以基本上你可以通过以下比较来确定单次读取大文件时哪个更快:

mmap 方法隐含的每页额外工作是否比使用read() 隐含的将文件内容从内核复制到用户空间的每字节工作成本更高? p>

在许多系统上,它们实际上是近似平衡的。请注意,每一个都具有完全不同的硬件和操作系统堆栈属性。

特别是,mmap 方法在以下情况下变得相对更快:

  • 操作系统具有快速的次要故障处理,尤其是次要故障批量优化,例如故障回避。
  • 操作系统有一个很好的MAP_POPULATE 实现,可以在底层页面在物理内存中连续的情况下有效地处理大型映射。
  • 硬件具有强大的页面翻译性能,例如大型 TLB、快速的二级 TLB、快速并行的 page-walker、良好的预取与翻译交互等。

...而read() 方法在以下情况下变得相对更快:

  • read() 系统调用具有良好的复制性能。例如,在内核端良好的copy_to_user 性能。
  • 内核有一种高效(相对于用户空间)映射内存的方式,例如,仅使用几个有硬件支持的大页面。
  • 内核具有快速的系统调用和一种跨系统调用保留内核 TLB 条目的方法。

上述硬件因素在不同平台之间大不相同,即使在同一个系列中(例如,在 x86 代中,尤其是在细分市场中),而且肯定会跨架构(例如,ARM 与 x86 与 PPC)。

操作系统因素也在不断变化,双方的各种改进导致一种方法的相对速度大幅提升。最近的列表包括:

  • 如上所述,添加了故障处理,这确实有助于在没有 MAP_POPULATE 的情况下使用 mmap。
  • 在arch/x86/lib/copy_user_64.S 中添加快速路径copy_to_user 方法,例如,在快速时使用REP MOVQ,这确实有助于read() 案例。

幽灵和熔毁后更新

Spectre 和 Meltdown 漏洞的缓解措施大大增加了系统调用的成本。在我测量过的系统上,“什么都不做”系统调用的成本(这是对系统调用的纯开销的估计,除了调用所做的任何实际工作)从典型的大约 100 ns现代Linux系统约700纳秒。此外,根据您的系统,专门针对 Meltdown 的 page-table isolation 修复可能会产生额外的下游影响,除了由于需要重新加载 TLB 条目而导致的直接系统调用成本。

与基于mmap 的方法相比,所有这些对于基于read() 的方法来说都是一个相对劣势,因为read() 方法必须对每个“缓冲区大小”的数据进行一次系统调用。您不能随意增加缓冲区大小来分摊此成本,因为使用大缓冲区通常性能更差,因为您超过了 L1 大小,因此经常遭受缓存未命中。

另一方面,使用mmap,您可以使用MAP_POPULATE 映射到一个大的内存区域并高效地访问它,而只需一次系统调用。


1 这或多或少也包括文件未完全缓存开始的情况,但操作系统预读足够好使其看起来如此(即,页面通常在您想要的时候被缓存)。这是一个微妙的问题,因为预读的工作方式在 mmap 和 read 调用之间通常有很大不同,并且可以通过 2 中所述的“建议”调用进一步调整。

2 ...因为如果文件没有缓存,您的行为将完全由 IO 问题主导,包括您的访问模式对底层硬件——你所有的努力都应该是确保这种访问尽可能有同情心,例如通过使用 madvise 或 fadvise 调用(以及您可以进行的任何应用程序级别更改以改进访问模式)。

3您可以绕过这个问题,例如,在较小的窗口中按顺序mmaping,例如 100 MB。

4 事实上,MAP_POPULATE 方法(至少是一些硬件/操作系统组合)只比不使用它快一点,可能是因为内核使用了faultaround - 所以小故障的实际数量减少了 16 倍左右。

【讨论】:

  • 感谢您为这个复杂问题提供更细致入微的答案。大多数人似乎很明显 mmap 更快,但实际上并非如此。在我的实验中,使用内存索引随机访问一个 100GB 的大型数据库结果证明使用 pread() 更快,即使我为数百万次访问中的每一个都分配了一个缓冲区。而且好像业内有不少人have observed the same。
  • 是的,这在很大程度上取决于场景。如果您的读取足够小并且随着时间的推移您倾向于重复读取相同的字节,mmap 将具有不可逾越的优势,因为它避免了固定的内核调用开销。另一方面,mmap 也增加了 TLB 压力,实际上使当前进程中第一次读取字节的“预热”阶段变慢(尽管它们仍在页面页面中),因为它可能比read 做更多的工作,例如“故障处理”相邻页面......对于相同的应用程序,“热身”才是最重要的! @CaetanoSauer
  • 我认为你所说的“......但是 250 亿个页面错误仍然不会超快......”它应该是“......但是 25 百万 页面错误仍然不会超快......”。我不是 100% 肯定的,所以这就是我不直接编辑的原因。
【解决方案3】:

主要的性能成本将是磁盘 i/o。 "mmap()" 肯定比 istream 快,但差异可能并不明显,因为磁盘 i/o 将支配您的运行时间。

我尝试了 Ben Collins 的代码片段(见上/下)来测试他的断言,即“mmap() 方式 更快”并且没有发现任何可测量的差异。在他的回答中查看我的 cmets。

我当然不建议单独依次映射每条记录,除非您的“记录”很大 - 这将非常慢,每条记录需要 2 次系统调用,并且可能会丢失页面磁盘内存缓存......

在您的情况下,我认为 mmap()、istream 和低级 open()/read() 调用都差不多。在这些情况下,我会推荐 mmap():

  1. 文件中存在随机访问(非顺序),并且
  2. 整个内容可以轻松地放入内存中,或者文件中存在局部性引用,因此可以映射某些页面并映射出其他页面。这样,操作系统就可以最大限度地利用可用 RAM。
  3. 或者,如果多个进程正在读取/处理同一个文件,那么 mmap() 非常棒,因为这些进程都共享相同的物理页面。

(顺便说一句 - 我喜欢 mmap()/MapViewOfFile())。

【讨论】:

  • 关于随机访问的要点:这可能是推动我认知的原因之一。
  • 我不会说文件必须舒适地放入内存中,只需放入地址空间即可。所以在 64 位系统上,应该没有理由不映射大文件。操作系统知道如何处理它;它与用于交换的逻辑相同,但在这种情况下不需要磁盘上的额外交换空间。
  • @MvG :你了解磁盘 i/o 的意义吗?如果文件适合地址空间但不适合内存,并且您可以进行随机访问,那么您可能需要进行磁盘磁头移动和查找或 SSD 页面操作的每个记录访问,这将对性能造成灾难。
  • 磁盘 i/o 方面应该独立于访问方法。如果您确实可以随机访问大于 RAM 的文件,则 mmap 和 seek+read 都严重受磁盘限制。否则两者都将从缓存中受益。与内存大小相比,我不认为文件大小是任何一个方向的有力论据。另一方面,文件大小与地址空间是一个非常有力的论据,尤其是对于真正的随机访问。
  • 我原来的答案有并且有这一点:“整个事情都适合在内存中或者文件中存在引用位置”。所以第二点解决了你在说什么。
【解决方案4】:

mmap 方式更快。你可以写一个简单的基准来证明给自己看:

char data[0x1000];
std::ifstream in("file.bin");

while (in)
{
  in.read(data, 0x1000);
  // do something with data
}

对比:

const int file_size=something;
const int page_size=0x1000;
int off=0;
void *data;

int fd = open("filename.bin", O_RDONLY);

while (off < file_size)
{
  data = mmap(NULL, page_size, PROT_READ, 0, fd, off);
  // do stuff with data
  munmap(data, page_size);
  off += page_size;
}

显然,我省略了细节(例如,如果您的文件不是 page_size 的倍数,如何确定何时到达文件末尾),但它确实不应该比这复杂得多。

如果可以的话,您可能会尝试将数据分解为多个文件,这些文件可以全部而不是部分进行 mmap() 编辑(更简单)。

几个月前,我对 boost_iostreams 的滑动窗口 mmap()-ed 流类进行了半生不熟的实现,但没人关心,我忙于其他事情。最不幸的是,几周前我删除了一个旧的未完成项目的档案,那是受害者之一:-(

更新:我还应该补充一点,这个基准在 Windows 中看起来会完全不同,因为 Microsoft 实现了一个漂亮的文件缓存,它首先可以完成您使用 mmap 所做的大部分工作。也就是说,对于经常访问的文件,你可以只做 std::ifstream.read() ,它会和 mmap 一样快,因为文件缓存已经为你做了一个内存映射,而且它是透明的。

最终更新:看,人们:在操作系统和标准库以及磁盘和内存层次结构的许多不同平台组合中,我不能确定系统调用mmap,被视为一个黑匣子,总是总是比read 快得多。这并不完全是我的意图,即使我的话可以这样理解。 最后,我的观点是内存映射 i/o 通常比基于字节的 i/o 快;这仍然是正确的。如果您通过实验发现两者之间没有区别,那么在我看来唯一合理的解释是您的平台以有利于调用read 的方式在幕后实现内存映射。绝对确定您以可移植方式使用内存映射 i/o 的唯一方法是使用mmap。如果您不关心可移植性并且可以依赖目标平台的特定特性,那么使用read 可能是合适的,而不会牺牲任何性能。

编辑以清理答案列表: @jbl:

滑动窗口 mmap 声音 有趣的。你能多说一点吗 关于它?

当然 - 我正在为 Git 编写一个 C++ 库(如果你愿意的话,是一个 libgit++),我遇到了与此类似的问题:我需要能够打开大(非常大)文件并且没有性能一条狗(就像std::fstream一样)。

Boost::Iostreams 已经有一个 mapped_file 源,但问题是它是 mmapping 整个文件,这将您限制为 2^(wordsize)。在 32 位机器上,4GB 不够大。期望 Git 中的 .pack 文件变得比这大得多并不是不合理的,所以我需要在不使用常规文件 i/o 的情况下分块读取文件。在Boost::Iostreams的掩护下,我实现了一个Source,这或多或少是std::streambuf和std::istream交互的另一种看法。您也可以尝试类似的方法,将std::filebuf 继承到mapped_filebuf 中,类似地,将std::fstream 继承到a mapped_fstream 中。两者之间的互动很难做到正确。 Boost::Iostreams 为您完成了一些工作,它还为过滤器和链提供了钩子,所以我认为这样实现它会更有用。

【讨论】:

  • RE:Windows 上的映射文件缓存。确切地说:启用文件缓冲后,内核内存会在内部映射您正在读取的文件,读入该缓冲区并将其复制回您的进程。就像你自己记忆映射一样,除了额外的复制步骤。
  • 我不愿意不同意已接受的答案,但我相信这个答案是错误的。我按照您的建议在 64 位 Linux 机器上尝试了您的代码,并且 mmap() 并不比 STL 实现快。此外,理论上我不希望 'mmap()' 更快(或更慢)。
  • @Tim Cooper:您可能会发现此线程 (markmail.org/message/…) 很有趣。请注意两件事:mmap 在 Linux 中没有得到适当的优化,并且还需要在测试中使用 madvise 以获得最佳结果。
  • 亲爱的本:我已阅读该链接。如果 'mmap()' 在 Linux 上不快,而 MapViewOfFile() 在 Windows 上不快,那么你能说“mmap 更快”吗?另外,出于理论上的原因,我认为 mmap() 对于顺序读取来说并不快 - 你有什么相反的解释吗?
  • Ben,为什么要麻烦mmap() 文件一次一页?如果 size_t 的容量足以容纳文件的大小(很可能在 64 位系统上),那么只需 mmap() 一次调用即可处理整个文件。
【解决方案5】:

很抱歉 Ben Collins 丢失了他的滑动窗口 mmap 源代码。如果能在 Boost 中使用那就太好了。

是的,映射文件要快得多。您实际上是在使用操作系统虚拟内存子系统将内存与磁盘关联起来,反之亦然。这样想:如果操作系统内核开发人员可以让它更快,他们会的。因为这样做几乎可以让一切变得更快:数据库、启动时间、程序加载时间等等。

滑动窗口方法实际上并不难,因为可以一次映射多个连续页面。因此,只要任何单个记录中最大的一个可以放入内存,记录的大小就无关紧要。重要的是管理簿记。

如果记录不是从 getpagesize() 边界开始,您的映射必须从前一页开始。映射区域的长度从记录的第一个字节(必要时向下舍入到 getpagesize() 的最接近的倍数)延伸到记录的最后一个字节(向上舍入到 getpagesize() 的最接近的倍数)。处理完一条记录后,您可以 unmap() 它,然后继续下一条。

这一切在 Windows 下也可以正常工作,使用 CreateFileMapping() 和 MapViewOfFile()(以及 GetSystemInfo() 来获取 SYSTEM_INFO.dwAllocationGranularity --- 不是 SYSTEM_INFO.dwPageSize)。

【讨论】:

  • 我刚刚在谷歌上搜索并发现了这个关于 dwAllocationGranularity 的小 sn-p——我正在使用 dwPageSize 并且一切都被打破了。谢谢!
【解决方案6】:

mmap 应该更快,但我不知道多少。这在很大程度上取决于您的代码。如果您使用 mmap,最好一次对整个文件进行 mmap,这将使您的生活更轻松。一个潜在的问题是,如果您的文件大于 4GB(或者实际上限制较低,通常为 2GB),您将需要 64 位架构。所以如果你使用的是 32 的环境,你可能不想使用它。

话虽如此,但可能有更好的方法来提高性能。你说输入文件被扫描了很多次,如果你可以一次读取它然后完成它,那可能会快得多。

【讨论】:

    【解决方案7】:

    也许您应该对文件进行预处理,因此每条记录都在一个单独的文件中(或者至少每个文件的大小都可以映射)。

    您还可以在处理下一条记录之前对每条记录执行所有处理步骤吗?也许这样可以避免一些 IO 开销?

    【讨论】:

      【解决方案8】:

      我同意 mmap 的文件 I/O 会更快,但是在您对代码进行基准测试时,反例不应该在某种程度上优化吗?

      本·柯林斯写道:

      char data[0x1000];
      std::ifstream in("file.bin");
      
      while (in)
      {
          in.read(data, 0x1000);
          // do something with data 
      }
      

      我建议也尝试一下:

      char data[0x1000];
      std::ifstream iifle( "file.bin");
      std::istream  in( ifile.rdbuf() );
      
      while( in )
      {
          in.read( data, 0x1000);
          // do something with data
      }
      

      除此之外,您还可以尝试使缓冲区大小与一页虚拟内存的大小相同,以防 0x1000 不是您机器上一页虚拟内存的大小...恕我直言,mmap'd 文件 I /O 仍然获胜,但这应该会让事情更接近。

      【讨论】:

        【解决方案9】:

        我记得几年前将一个包含树结构的巨大文件映射到内存中。与涉及大量内存工作的正常反序列化相比,我对速度感到惊讶,例如分配树节点和设置指针。 所以实际上我是在比较一个对 mmap 的调用(或它在 Windows 上的对应) 反对对 operator new 和构造函数调用的许多(许多)调用。 对于此类任务,与反序列化相比,mmap 是无与伦比的。 当然,应该为此研究 boosts 可重定位指针。

        【讨论】:

        • 这听起来更像是灾难的秘诀。如果对象布局发生变化,你会怎么做?如果你有虚函数,所有的 vftbl 指针都可能是错误的。您如何控制文件映射到的位置?你可以给它一个地址,但这只是一个提示,内核可能会选择另一个基地址。
        • 当您拥有稳定且明确定义的树形布局时,这将非常有效。然后,您可以将所有内容转换为相关结构,并通过每次添加“mmap 起始地址”的偏移量来跟踪内部文件指针。这与使用 inode 和目录树的文件系统非常相似
        【解决方案10】:

        这听起来像是多线程的一个很好的用例...我认为您可以很容易地设置一个线程来读取数据,而其他线程处理它。这可能是一种显着提高感知性能的方法。只是一个想法。

        【讨论】:

        • 是的。我一直在考虑这个问题,可能会在以后的版本中尝试一下。我唯一的保留是处理比 I/O 延迟要短得多,所以可能没有太多好处。
        【解决方案11】:

        在我看来,使用 mmap()“只是”减轻了开发人员编写自己的缓存代码的负担。在一个简单的“每次读取文件”的情况下,这并不难(尽管 mlbrock 指出您仍然将内存副本保存到进程空间中),但是如果您在文件中来回切换或跳过位等等,我相信内核开发人员可能在实现缓存方面做得比我做得更好......

        【讨论】:

        • 在缓存特定于应用程序的数据方面,您很可能比内核做得更好,内核以非常盲目的方式对页面大小的块进行操作(例如,它只使用简单的伪LRU 方案来决定驱逐哪些页面) - 虽然您可能对正确的缓存粒度了解很多,并且对未来的访问模式也有很好的了解。 mmap 用于缓存的真正好处是您只需重新使用已经存在的现有页面缓存,因此您可以免费获得该内存,并且可以跨进程共享也是。
        【解决方案12】:

        我认为 mmap 最大的优点是具有异步读取的潜力:

            addr1 = NULL;
            while( size_left > 0 ) {
                r = min(MMAP_SIZE, size_left);
                addr2 = mmap(NULL, r,
                    PROT_READ, MAP_FLAGS,
                    0, pos);
                if (addr1 != NULL)
                {
                    /* process mmap from prev cycle */
                    feed_data(ctx, addr1, MMAP_SIZE);
                    munmap(addr1, MMAP_SIZE);
                }
                addr1 = addr2;
                size_left -= r;
                pos += r;
            }
            feed_data(ctx, addr1, r);
            munmap(addr1, r);

        问题是我找不到正确的 MAP_FLAGS 来提示此内存应尽快从文件同步。 我希望 MAP_POPULATE 为 mmap 提供正确的提示(即它不会在调用返回之前尝试加载所有内容,而是会在异步中使用 feed_data 进行加载)。至少它使用此标志提供了更好的结果,即使手册声明自 2.6.23 以来没有 MAP_PRIVATE 它什么也不做。

        【讨论】:

        • 你想要 posix_madvise with the WILLNEED 标志来预填充惰性提示。
        • @ShadowRanger,听起来很合理。虽然我会更新手册页以明确指出 posix_madvise 是异步调用。对于那些想要等到整个内存区域可用而没有页面错误的人来说,也可以参考mlock。
        猜你喜欢
        • 2021-08-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-08
        • 1970-01-01
        • 1970-01-01
        • 2014-02-21
        • 2019-03-21
        相关资源
        最近更新 更多