在 700,000 个矢量元素处突然出现内存峰值

Sudden memory spike at 700,000 vector elements

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

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

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

谁能给我解释一下?

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

总使用率明显低了很多,但思路是一样的。

矢量大小和容量

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

尺寸()

Returns当前元素个数

空()

Returns容器是否为空(相当于0==size()但速度更快)

容量()

Returns无需重新分配的最大可能元素数

储备(数量)

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

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

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

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

当您向空向量添加元素时,它会通过 new 为多个元素分配足够的 space。也许像 16 岁。这样做是因为将数组的大小调整到更大的缓冲区很慢,所以它分配的比它需要的多。如果它为 16 个元素分配空间,这意味着您可以在它需要再次调用 new 之前再推回 15 个。每次它都会显着增长。如果您有 500 个元素(并且它没有空间)并且您再推回一个,它可能会分配 750 个空间。甚至可能是 1000 个。或 2000 个。足够的空间。

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

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

未设置发生这种情况的向量大小,它取决于还分配了什么,以及分配和释放它们的顺序。即使是您 delete 所做的事情仍然会影响事情,这非常复杂。此外,vector 的增长速度取决于你的库实现,程序的内存管理器从 OS 中获取的内存量也取决于我不知道的因素。

我不知道为什么 GPU 的内存会激增,这取决于您对程序执行的操作。但请注意,GPU 内存总量较少,它完全有可能比 "private bytes".

增长 更小 数量

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