19

以另一种方式问这个问题,您能否确认当您 mmap() 一个文件时,您实际上访问了页面缓存中已经存在的确切物理页面?

我之所以问,是因为我正在一台 192 核、1TB RAM 的机器上测试一个 400GB 的数据文件,该文件在测试前预缓存到页面缓存中(只需删除缓存,然后对文件执行 md5sum) .

最初,我让所有 192 个线程分别对文件进行 mmap,假设它们都会(基本上)返回相同的内存区域(或者可能是相同的内存区域,但以某种方式映射了多次)。因此,我假设使用两个不同映射到同一个文件的两个线程都可以直接访问相同的页面。(让我们忽略这个例子中的 NUMA,尽管显然它在更高的线程数中很重要。)

然而,在实践中,我发现当每个线程单独映射文件时,在更高的线程数下性能会变得很糟糕。当我们删除它并只执行一个传递给线程的 mmap 时(这样所有线程都直接访问相同的内存区域),然后性能显提高。

这一切都很好,但我正试图找出原因。如果实际上映射一个文件只是授予对现有页面缓存的直接访问权限,那么我认为映射它的次数并不重要——它应该都到同一个地方。

但是考虑到这样的性能成本,在我看来,实际上每个 mmap 都是独立且冗余地填充的(可能通过从页面缓存复制,或者可能通过再次从磁盘读取)。

您能否评论一下为什么我看到共享访问同一内存与映射同一文件之间存在如此不同的性能?

谢谢,感谢您的帮助!

4

1 回答 1

7

我想我找到了答案,它处理页面目录。答案是肯定的,同一文件的两个映射区域将访问相同的底层页面缓存数据。但是,每个映射都需要独立地将每个虚拟页面映射到物理页面——这意味着页面目录中的条目数量是访问相同 RAM 的 2 倍。

基本上,每个 mmap() 都会在虚拟内存中创建一个新范围。该范围的每一页对应于物理内存的一页,并且该映射存储在分层页目录中——每 4KB 页有一个条目。因此,大区域的每个 mmap() 都会在页面目录中生成大量条目。

我的猜测是它实际上并没有预先定义它们,这就是为什么 mmap() 甚至可以立即调用一个巨大的文件。但随着时间的推移,它可能必须建立这些条目,因为在映射范围上存在错误,这意味着随着时间的推移它会被填写。填充页面目录的这项额外工作可能是使用不同 mmap 的线程比共享相同 mmap 的线程慢的原因。而且我敢打赌,内核在取消映射范围时需要擦除所有这些条目——这就是 unmmap() 如此缓慢的原因。

(还有翻译后备缓冲区,但这是每个 CPU 的,所以我认为这在这里并不重要。)

无论如何,听起来重新映射同一个区域只会增加额外的开销,因为在我看来没有任何收获。

于 2017-09-17T04:20:16.453 回答