【问题标题】:Is it possible to partially free dynamically-allocated memory on a POSIX system?是否可以在 POSIX 系统上部分释放动态分配的内存?
【发布时间】:2015-09-16 19:06:00
【问题描述】:

我有一个 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-th 元素,应用程序就不再需要它了。

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

我的应用程序在符合 POSIX 的操作系统(例如 Linux、OS X)上运行。有没有什么接口可以让操作系统只从内存块的前面释放一个指定的区域?这似乎是最有效的方法,因为我可以通知内存管理器,例如,内存块的前 2 GB 可以在我完成后回收。

【问题讨论】:

  • 一开始你会一次性分配所有内存吗?如果是:这有必要吗?如果没有:您是否尝试过环形缓冲区?
  • 是的,你似乎需要一个循环缓冲区。
  • @Gombat:我应该把这个细节放在 OP 中,但是我的处理链中的更早一步要求整个缓冲区都在内存中(作为一个连续的块,因为它被传递给 C API) 一次。否则是的,循环缓冲区肯定是正确的选择。我在问题中提到的算法是对多个处理步骤的管道的结论;完成后,我不再需要数据。
  • @JasonR 您应该将此信息编辑到问题中,这很重要。
  • @JasonR:您能否更具体地说明您实际想要实现的目标?整个计算需要多长时间,系统通常内存不足,还是您只是想“减少运行 top 时显示的数字”? [是的,我意识到我已经写了一个答案!]

标签: c++ memory memory-management posix


【解决方案1】:

如果您的整个缓冲区必须一次在内存中,那么您可能不会从稍后部分释放它中获得太多收益。

这篇文章的主要观点基本上是不要告诉你做你想做的事,因为如果你的应用程序的内存实际上并不需要,操作系统不会不必要地将它保留在 RAM 中。这就是“常驻内存使用”和“虚拟内存使用”的区别。 “驻留”是当前使用的内存,在 RAM 中,“虚拟”是应用程序的总内存使用量。只要您的交换分区足够大,“虚拟”内存就几乎不是问题。 [我在这里假设您的系统不会耗尽虚拟内存空间,这在 64 位应用程序中是正确的,只要您不使用数百 TB 的虚拟空间!]

如果你仍然想这样做,并且想要有一些合理的可移植性,我建议构建一个行为类似于std::vector 的“包装器”,并分配几兆字节(或几千兆字节)的块一次记忆,然后是这样的:

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

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

如果这对您有所帮助,我会感到非常惊讶,除非do_algorithm(buf[i]) 需要相当长的时间(肯定是几秒钟,可能是几分钟甚至几小时)。当然,只有当你真的有其他有用的东西与那个记忆有关时,它才会有所帮助。即使这样,如果系统内存不足,操作系统也会通过将其换出到磁盘来回收未积极使用的内存。

换句话说,如果你分配 100GB,填充它,让它静置不动,它最终将全部在硬盘上而不是在 RAM 中。

此外,应用程序中的堆保留已释放的内存,并且操作系统在应用程序退出之前不会取回内存,这并不罕见 - 当然,如果仅释放较大分配的一部分,则在整个块被释放之前,运行时不会释放它。因此,如开头所述,我不确定这实际上会对您的应用程序有多大帮助。

与有关“调整”和“性能改进”的所有内容一样,您需要衡量和比较基准,看看它有多大帮助。

【讨论】:

    【解决方案2】:

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

    你不能使用malloc()/realloc()/free()来做到这一点。

    但是,您可以使用mmap()munmap() 以半便携式方式进行操作。关键是如果你munmap()某个页面,malloc()以后可以使用那个页面:

    • 使用mmap() 创建匿名映射;
    • 对于不再需要的区域,请随后致电 munmap()

    可移植性问题是:

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

    更新

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

      • 这里不需要MAP_FIXED,因为您可以使用单个mmap() 调用创建整个区域,然后使用unmap() 的一部分。 MAP_FIXED 在 POSIX 中指定,但没有可移植的方法来确定它的正确地址。所以MAP_FIXED 增加了可移植性问题,并没有解决现有问题(您仍然需要不可移植的匿名映射)。
      • @g-v 是的,我想我已经在“如果你预先分配整个内存,……”部分解决了这个问题。
      • 是的,我明白了。只是关于可移植性的说明。
      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 2011-03-17
        • 2023-04-07
        • 2016-02-06
        • 2013-11-22
        • 2014-05-27
        • 2014-01-06
        • 1970-01-01
        相关资源
        最近更新 更多