【问题标题】:Sudden memory spike at 700,000 vector elements在 700,000 个向量元素处突然出现内存峰值
【发布时间】:2015-04-01 22:04:25
【问题描述】:

所以我在我的程序中运行了一个内存使用测试,其中我将每个元素添加到每个帧的两个单独的向量 (~60fps) 中的 20 个元素。我预计在某个时候我会开始看到内存泄漏,但内存使用量一直保持不变,直到某个临界点。总共有大约 700,000 个元素,它飙升,然后在新的高原再次趋于平稳。

我感觉这与此时自动增加的向量分配有关,但我不确定,也无法在网上找到任何东西。它也不能真正解释为什么当时分配了这么多额外的内存(CPU 上的私有字节从 ~800 跳到 ~900,系统 GPU 内存从 ~20 跳到 ~140)。以下是 CPU 和 GPU 的 Process Explorer 图:

注意:CPU 和 GPU 使用率的下降是因为我在看到峰值后暂停了程序。

谁能给我解释一下?

编辑:这是一个更简单、更通用的测试:

总使用量显然要低很多,但思路相同。

【问题讨论】:

  • 如果您确定这不是内存泄漏,那么最好的方法是保留程序可以达到的最大向量。
  • 这取决于您测试它的工具,但它可能与您的应用程序分配请求新的内存页面文件有关(因为并非每个分配真的都会改变应用程序的内存大小(以页面文件为单位)。
  • @Tony J 我的程序不需要向量中有这么多元素。我只是对向量的内存管理产生了好奇,所以我随机地把它扔了进去。我试图理解的是为什么它保持稳定然后出现峰值,而不是稳定地泄漏。
  • 再一次,我认为这不一定是向量的错误(在您的情况下,虽然不是一般情况下),但需要的整体进程内存导致为您的应用程序分配新的页面文件操作系统。
  • @Amon Zilca:谢谢,这似乎是件好事。您是否碰巧有任何关于其工作原理的好资源(比谷歌搜索会更好)?

标签: c++ memory memory-management vector


【解决方案1】:

当您向空向量添加元素时,它将通过new 为多个元素分配足够的空间。可能像 16 岁。这样做是因为将数组大小调整为更大的缓冲区很慢,因此它分配的内存超出了它的需要。如果它为 16 个元素分配空间,这意味着您可以再推迟 15 个元素,然后它需要再次调用 new。每次它都会显着增长。如果您有 500 个元素(并且空间不足)并且您再推回一个,它可能会为 750 个或什至 1000 个或 2000 个分配空间。足够的空间。

事实证明,当你(或向量)调用new 时,你会从程序的内存管理器中得到这个。如果程序的内存管理器没有足够的可用内存,它会向操作系统请求大量内存,因为操作系统调用本身很慢,而处理页面映射很慢。因此,当vector 要求为200 字节提供空间时,程序的内存管理器可能实际上抓取 65536 个字节,然后只给向量其中的 200 个字节,并将剩余的 65336 个字节保存为下次致电new。正因为如此,你(或向量)可以多次调用new,然后你不得不再次打扰操作系统,事情进展很快。

但这有一个副作用:操作系统实际上无法判断您的程序真正使用了多少内存。它只知道你从它分配了 65536,所以它报告了这一点。当您推回向量中的元素时,最终向量会耗尽容量,并要求程序的内存管理器提供更多信息。随着它越来越多地这样做,操作系统报告相同的内存使用情况,因为它看不到。 最终内存管理器耗尽了容量,并要求操作系统提供更多容量。操作系统分配了另一个大块(65536?131072?),您会看到内存使用量突然大幅飙升。

未设置发生这种情况的向量大小,这取决于还分配了哪些其他内容,以及分配和释放它们的顺序。即使你deleted 的事情仍然会影响事情,这非常复杂。此外,向量的增长速度取决于您的库实现,以及程序的内存管理器从操作系统获取的内存量也取决于我不知道的因素。

我不知道为什么 GPU 的内存会飙升,这取决于您对程序执行的操作。但请注意,GPU 内存总量较少,它完全有可能比“私有字节”增长更小的数量。

【讨论】:

  • new is 有点慢,但这不是这种行为的原因。比new 渐进地慢的是需要将元素从旧缓冲区复制到新分配的缓冲区中。该行为需要保证向量的渐近运行时间,而不是简单地提高绝对运行时间。
  • @KonradRudolph:已修复。
【解决方案2】:

向量使用动态分配的数组来存储它们的元素。该数组可能需要重新分配以便在插入新元素时增大大小,这意味着分配一个新数组并将所有元素移动到其中。就处理时间而言,这是一项相对昂贵的任务,因此,向量不会在每次将元素添加到容器时重新分配。相反,向量容器可能会分配一些额外的存储空间来适应可能的增长,因此容器的实际容量可能大于包含其元素所严格需要的存储空间(即其大小)这解释了你的高原。容量扩大是通过将当前容量增加一倍来完成的。可能的情况是,在有限数量的翻倍之后,其容量翻了两番。 这将解释你的峰值。

【讨论】:

    【解决方案3】:

    向量大小和容量

    通过分配比以往更多的内存来获得良好的性能。

    尺寸()

    返回当前元素个数

    空()

    返回容器是否为空(相当于 0==size() 但更快)

    容量()

    返回不重新分配的最大可能元素数

    保留(数量)

    扩大容量,如果还不够的话

    向量的容量很重要,因为 重新分配使元素的所有引用、指针和迭代器无效。 通过将所有元素移动到新的堆位置,重新分配需要时间。 重新分配大小的增量取决于实际的向量实现。

    使用reserve(num)的代码示例:

    std::vector<int> v1;  // Create an empty vector    
    v1.reserve(80);       // Reserve memory for 80 elements
    

    【讨论】:

      猜你喜欢
      • 2013-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-13
      • 1970-01-01
      • 2021-12-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多