是否可以在 POSIX 系统上部分释放动态分配的内存?

Is it possible to partially free dynamically-allocated memory on a POSIX system?

我有一个 C++ 应用程序,有时我需要在内存中保存一个大的 POD 类型缓冲区(例如 25 billion floats 的数组)一次在一个连续的块中。这种特殊的内存组织是由应用程序使用一些对数据进行操作的 C API 这一事实驱动的。因此,不同的安排(例如像 std::deque 使用的较小内存块的列表)是不可行的。

应用程序有一个算法,该算法以流方式在数组上 运行;像这样想:

std::vector<float> buf(<very_large_size>);
for (size_t i = 0; i < buf.size(); ++i) do_algorithm(buf[i]);

此特定算法是已应用于数据集的早期处理步骤流水线的结论。因此,一旦我的算法传递了数组中的第 i 个元素,应用程序就不再需要它了。

因此,理论上,我可以释放该内存,以减少我的应用程序在处理数据时的内存占用。但是,执行类似于 realloc()(或 std::vector<T>::shrink_to_fit())的操作会效率低下,因为我的应用程序必须在重新分配时花费时间将未使用的数据复制到新位置。

我的应用程序 运行s 在 POSIX 兼容操作系统上运行(例如 Linux、OS X)。是否有任何接口可以让操作系统仅释放内存块前面的指定区域?这似乎是最有效的方法,因为我可以通知内存管理器,例如,一旦我完成它,就可以回收内存块的前 2 GB。

如果您的整个缓冲区必须立即在内存中,那么稍后部分释放它可能不会有太大好处。

这个 post 的要点基本上是不告诉你做你想做的事,因为如果实际上不需要,OS 不会不必要地将你的应用程序内存保留在 RAM 中.这就是"resident memory usage"和"virtual memory usage"的区别。 "Resident" 是当前使用的内存,"virtual" 是应用程序的总内存使用量。只要您的交换分区足够大,"virtual" 内存就几乎不是问题。 [我在这里假设您的系统不会 运行 虚拟内存不足 space,这在 64 位应用程序中是正确的,只要您不使用数百 TB 的虚拟内存 space!]

如果你仍然想这样做,并且想要有一些合理的可移植性,我建议构建一个 "wrapper" 其行为有点像 std::vector 并分配一些兆字节的块(或者可能几千兆字节)的内存,然后是这样的:

 for (size_t i = 0; i < buf.size(); ++i) {
    do_algorithm(buf[i]);
    buf.done(i);
 }

done 方法将简单地检查 if i 的值是否(一个元素)超过当前缓冲区的末尾,然后释放它。 [这应该很好地内联,并且在平均循环上产生的开销很小——当然假设元素实际上是按线性顺序使用的]。

如果这对您有任何帮助,我会感到非常惊讶,除非 do_algorithm(buf[i]) 需要相当长的时间(当然很多秒,可能很多分钟甚至几个小时)。当然,只有当你真的有其他有用的东西可以处理那段记忆时,它才会有所帮助。即便如此,如果系统内存不足,OS 也会通过将未使用的内存换出到磁盘来回收未使用的内存。

换句话说,如果你分配 100GB,填满它,让它坐着不动,它最终会全部在硬盘上而不是在 RAM 中。

此外,应用程序中的堆保留释放的内存,并且 OS 在应用程序退出之前不会取回内存,这一点也不奇怪 - 当然,如果只是部分较大的分配被释放,运行时间将在整个块被释放之前不会释放它。因此,如开头所述,我不确定这对您的申请有多大实际帮助。

关于 "tuning" 和 "performance improvements" 的所有内容,您需要衡量和比较基准,看看它有多大帮助。

如果你可以不用 std::vector 的便利(无论如何在这种情况下不会给你太多,因为你永远不想复制/return / 移动那个野兽) ,您可以进行自己的内存处理。向操作系统询问整页内存(通过 mmap)并根据需要 return(使用 munmap)。您可以通过其第一个参数和可选的 MAP_FIXED 标志告诉 mmap 将页面映射到特定地址(当然,您必须确保该地址未被其他方式占用),这样您就可以建立一个区域的连续内存。如果您预先分配整个内存,那么这不是问题,您可以使用单个 mmap 来完成,让操作系统选择一个方便的位置来映射它。最后,这就是 malloc 内部所做的。对于没有 sys/mman.h 的平台,如果您可以接受在这些平台上不会 return 内存过早这一事实,那么退回到使用 malloc 并不难。

我怀疑如果您的分配大小始终是页面大小的倍数,realloc 将足够聪明,不会复制任何数据。不过,您必须尝试一下,看看它是否适用于您的特定目标平台(或查阅您的 malloc 的文档)。

Is it possible to partially free dynamically-allocated memory on a POSIX system?

您不能使用 malloc()/realloc()/free().

但是,您可以使用 mmap()munmap() 以半便携方式完成此操作。关键是,如果您 munmap() 某个页面,malloc() 以后可以使用该页面:

  • 使用 mmap() 创建匿名映射;
  • 随后为您不再需要的区域调用 munmap()

可移植性问题是:

  • POSIX 不指定匿名映射。一些系统提供 MAP_ANONYMOUSMAP_ANON 标志。其他系统提供可以为此目的映射的特殊设备文件。 Linux 两者都提供。
  • 我认为 POSIX 不能保证当您 munmap() 一个页面时,malloc() 将能够使用它。但我认为它适用于具有 mmap()/unmap().
  • 的所有系统

更新

如果您的内存区域太大以至于大多数页面肯定会被写入交换区,那么使用文件映射而不是匿名映射不会丢失任何内容。文件映射在 POSIX.

中指定

mremap 可能就是您所需要的。只要您移动整个页面,您就可以执行超快速的重新分配(实际上内核会为您完成)。