c - 是否可以通过 mmap 匿名内存访问 "punch holes"?

标签 c linux mmap

考虑一个使用大量页面大小的内存区域(比如 64 kB 左右)的程序,每个内存区域的生命周期都相当短。 (在我的特定情况下,这些是绿色线程的备用堆栈。)

如何最好地分配这些区域,以便在该区域不再使用时可以将它们的页面返回给内核?天真的解决方案显然是简单地 mmap 每个区域,并在完成后立即再次 munmap 它们。不过,我觉得这是个坏主意,因为它们太多了。我怀疑 VMM 可能会在一段时间后开始严重扩展;但即使不是,我仍然对理论案例感兴趣。

如果我只是 mmap 我自己一个巨大的匿名映射,我从中按需分配区域,有没有办法通过该映射为我完成的区域“打洞” ?有点像 madvise(MADV_DONTNEED),但不同之处在于页面应该被视为已删除,因此内核实际上不需要将它们的内容保留在任何地方,而是可以在它们被删除时重用归零页面再次出错。

我正在使用 Linux,在这种情况下,我不会为使用特定于 Linux 的调用而烦恼。

最佳答案

我在某个时候对这个主题做了很多研究(用于不同的用途)。在我的例子中,我需要一个人口稀少的大型散列图 + 不时将其归零的能力。

mmap解决方案:

最简单的解决方案(可移植,madvise(MADV_DONTNEED) 是特定于 Linux 的)将这样的映射置零是到 mmap它上面的一个新映射。

 void * mapping = mmap(MAP_ANONYMOUS);
 // use the mapping

 // zero certain pages
 mmap(mapping +  page_aligned_offset, length, MAP_FIXED | MAP_ANONYMOUS);

最后一次调用在性能方面等同于后续的 munmap/mmap/MAP_FIXED , 但它是线程安全的。

在性能方面,此解决方案的问题在于页面必须在后续写访问时再次出现故障,这会发出中断和上下文更改。这只有在一开始就出现故障的页面很少的情况下才有效率。

memset解决方案:

如果必须取消映射大部分映射,在获得如此糟糕的性能后,我决定使用 memset 手动将内存归零。 .如果大约超过 70% 的页面已经出现故障(如果不是,它们发生在第一轮 memset 之后),那么这比重新映射这些页面要快。

mincore解决方案:

我的下一个想法是实际上只有 memset在之前出现故障的那些页面上。此解决方案不是线程安全的。打电话mincore确定页面是否出现故障,然后有选择地 memset它们为零是一个显着的性能改进,直到超过 50% 的映射出现故障,此时 memset整个映射变得更简单(mincore 是一个系统调用,需要一个上下文更改)。

incore 表解决方案:

然后我采用的最后一种方法是拥有自己的核心表(每页一位),说明自上次删除后是否使用过它。这是迄今为止最有效的方法,因为您实际上只会将实际使用的每一轮中的页面归零。它显然也不是线程安全的,需要您跟踪哪些页面已写入用户空间,但如果您需要这种性能,那么这是迄今为止最有效的方法。

关于c - 是否可以通过 mmap 匿名内存访问 "punch holes"?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21722545/

相关文章:

python - 尝试导入 pypyodbc 模块时出现错误 'ODBC Library is not found. Is LD_LIBRARY_PATH set?'

linux - 启用 MAP_FIXED 标志的 nmap EPERM 错误

c - fgets() 在从空行读取时重复前一个字符串

c - 在 C 中打印列

linux - 设置 SFTP 以使用公钥身份验证

c - ftruncate() 对大页面失败

python - 在 docker 容器之间共享 gensim 的 KeyedVectors 对象的内存

c++ - 在 C++ 兼容性更改后,G++ 生成的二进制文件比 GCC 小,但比之前的二进制文件稍大?

c - 如何将两个 uint32_t 值交错为一个 uint64_t?

linux - 如何检查是否已经安装了 BLAS 和 ATLAS