【问题标题】:how to manage large arrays如何管理大型阵列
【发布时间】:2012-04-20 11:21:10
【问题描述】:

我有一个 c++ 程序,它使用了几个非常大的双精度数组,我想减少程序这个特定部分的内存占用。目前,我分配了 100 个,每个可以是 100 Mb。

现在,我确实有一个优势,这些数组的一部分最终会在程序执行的后期部分变得过时,并且几乎不需要在任何时候将其中任何一个都保存在内存中。

我的问题是这样的:

在我用 new 或 malloc 创建数组后,有什么方法可以告诉操作系统它的一部分不再需要了? 我得出的结论是,实现这一目标的唯一方法是声明一个指针数组,每个指针都可能指向一个块,比如 1Mb 的所需数组,以便不再需要旧块可以重新用于数组的新位。在我看来,这就像编写一个自定义内存管理器,它看起来有点像大锤,也会对性能产生一些影响

我无法移动数组中的数据,因为它会导致过多的线程争用问题。数组可以随时被大量线程中的任何一个访问,尽管只有一个线程会写入任何给定的数组。

【问题讨论】:

  • 超大数组问题的答案通常是使用稀疏数组——一种看起来像数组的数据结构,但实际上只存储相关元素。如果数组的大部分是空白空间,则此方法有效。这是真的吗,或者在某些时候你真的需要给定数组中的所有元素?
  • 是否可以接受特定于平台的解决方案? Posix 允许您 mmap 一个大区域,然后在您完成它们后 munmap 部分区域。

标签: c++ optimization memory-management


【解决方案1】:

这取决于操作系统。 POSIX - 包括 Linux - 有系统调用 madvise 来提高内存性能。从手册页:

madvise() 系统调用建议内核如何处理地址范围内的分页输入/输出,地址范围从地址 addr 开始,大小为长度字节。它允许应用程序告诉内核它希望如何使用一些映射或共享内存区域,以便内核可以选择适当的预读和缓存技术。此调用不会影响应用程序的语义(MADV_DONTNEED 除外),但可能会影响其性能。内核可以随意忽略这些建议。

有关详细信息,请参阅 madvise 的手册页。

编辑:显然,上面的描述不够清楚。所以,这里有更多细节,其中一些是特定于 Linux 的。

您可以使用mmap 分配一块内存(直接从操作系统而不是libc),它没有任何文件支持。对于大块内存,malloc 正在做同样的事情。你必须使用munmap来释放内存——不管madvise的用法:

void* data = ::mmap(nullptr, size, PROT_READ | PROT_WRITE,
    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
// ...
::munmap(data, size);

如果你想删除这个块的某些部分,你可以使用madvise 告诉内核这样做:

madvise(static_cast<unsigned char*>(data) + 7 * page_size,
    3 * page_size, MADV_DONTNEED);

地址范围仍然有效,但不再支持 - 无论是物理 RAM 还是存储。如果您稍后访问这些页面,内核将动态分配一些新页面并将它们重新初始化为零。请注意,dontneed 页面也是进程虚拟内存大小的一部分。可能需要对虚拟内存管理进行一些配置更改,例如激活过度提交。

【讨论】:

  • 你不能freedelete之后的内存;可能会尝试重用进程不再可用的内存。
  • 只是查看 mmap 的手册页。使用 MAP_NORESERVE 与 MAP_ANONYMOUS 或两者调用有什么好处?
  • MAP_NORESERVE 是关于内存过度提交的,即初始内存分配是否会成功,尽管可能会发生,但以后无法提供。如果您打算立即使用内存,则不应使用 MAP_NORESERVE。
【解决方案2】:

如果我们有更多详细信息会更容易回答。

1°) 问题的答案“在我使用 new 或 malloc 创建数组后,有什么方法可以告诉操作系统它的一部分不再需要?”是“不是真的”。这就是 C 和 C++ 以及任何让您手动处理内存的语言的意义所在。

2°) 如果您使用的是 C++ 而不是 C,则不应使用 malloc。

3°) 也不是数组,除非出于非常特殊的原因。使用 std::vector。

4°) 最好,如果您需要经常更改数组的内容并减少内存占用,请使用链表 (std::list),尽管单独“访问”内容会更昂贵列表(但如果您只遍历它,速度几乎一样快)。

【讨论】:

    【解决方案3】:

    带有指向std::array&lt;double,LARGE_NUMBER&gt; 的指针的std::deque 可以完成这项工作,但您最好使用双端队列创建一个专用容器,这样您就可以重新映射索引,最重要的是,定义何时不再使用条目。

    专用容器还可以包含读/写锁,因此可以以线程安全的方式使用。

    【讨论】:

      【解决方案4】:

      您可以尝试使用列表而不是数组。当然 list 比 array 更“重”,但另一方面,重建一个 list 很容易,这样你就可以在它过时时扔掉它的一部分。您还可以使用一个包装器,它只包含索引,说明列表的哪一部分是最新的,哪一部分可以重用。 这将帮助您提高性能,但需要更多(可重用)内存。

      【讨论】:

      • 不列表必须按顺序访问。遍历列表以访问元素 1000000+ 会很慢
      • 如果一次只使用列表的一部分,这不会很慢。其实这取决于list的实现。 std 中的列表实现得非常优化,性能一定不会慢很多。
      【解决方案5】:

      在路上按块分配和delete[]-ing 和new[]-ing 似乎是一个很好的解决方案。尽可能少地进行内存管理是可能的。不要自己重用块,只需释放旧块并在需要时分配新块。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-06-12
        • 2013-09-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-25
        相关资源
        最近更新 更多