Visual C++ 矢量擦除会增加内存使用量?

Posted

技术标签:

【中文标题】Visual C++ 矢量擦除会增加内存使用量?【英文标题】:Visual C++ vector erase increases memory usage? 【发布时间】:2014-12-10 17:44:38 【问题描述】:

这是我在 *** 上的第一个问题。我在很大程度上寻找了以下代码行所遇到的原因:

unsigned long long _mem1= getUsedVirtualMemory();
vector.erase(vector.begin() + _idx);
contained= false; // don't stop the loop
_idx--; // the removed object has redefined the idx to be consider.

_mem1 = getUsedVirtualMemory() - _mem1;
if (_mem1 > 0) printf("Memory - 2 mem1: %lu\n" , _mem1);

我的程序中有大量内存消耗,经过密集的调试会话、一些 printfs 和耗时的分析,我到达了这一点:

getUsedVirtualMemory 使用以下代码实现:

PROCESS_MEMORY_COUNTERS_EX pmc;
GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*) &pmc, sizeof(pmc));
SIZE_T virtualMemUsedByMe = pmc.PrivateUsage;
return virtualMemUsedByMe;

获取进程分配的虚拟内存量;向量是对象的向量(不是指针)。

在大多数情况下,向量的擦除方法似乎按预期工作,但在某些情况下,该向量的擦除方法似乎增加了进程使用的内存而不是释放它。我在代码周围的很多情况下都使用 Windows 系统函数 GetProcessMemoryInfo 来调试这个问题,它似乎返回了已用虚拟内存的实际值。

我正在使用 Visual Studio C++ 2010 Professional。

如果需要更多信息,请告诉我。

感谢您的任何回复。

更新:

你在回复中写的一切都是正确的,我忘记了以下细节:

    我已经知道向量具有大小(元素的实际数量)和容量(分配的用于存储元素的插槽) 我已经知道擦除方法不会释放内存(我查找了很多关于该方法的文档) 最后,我稍后会在该向量中添加其他元素,因此我不需要缩小该向量。

实际的问题是,在这种情况下,最后一行代码中“_mem1”的值显示了1.600.000字节的差异:内存的不合理增加,而我预计是0字节。

此外,在操作后已用内存的值小于第一个的情况下,我希望在 Is unsigned integer subtraction defined behavior? 中解释的内容会有一个非常大的数字

我得到一个大于 0 但相对较短的值,而不是预期的结果。

为了更好地了解问题的发生率,在这段代码上迭代数千次,意外分配了大约 20 Gb 的虚拟内存。

【问题讨论】:

释放运行时库函数的分配并不一定会将任何东西释放回操作系统。内存分配器通常会保留一个可用块池来满足以后的分配,而不会浪费时间将内存交还给操作系统以便稍后重新请求它。因此,您没有看到内存使用量减少也就不足为奇了。 我绝对同意你的观点,但我无法证明“为什么擦除操作应该分配大约 1.5Mb 的额外内存?” 【参考方案1】:

一个向量有:

size(),表示容器中有多少活动元素 capacity(),告诉内存中保留了多少元素

erase() 将大小更改为零。它不会释放分配的内存容量。

您可以使用 shrink_to_fit() 确保容量减少到大小。

使用resize() 更改大小或使用reserve() 更改容量可能会在必要时增加分配的内存,但如果新的大小/容量低于现有容量,则不一定会释放内存。

【讨论】:

不幸的是,shrink_to_fit() 是一个不具约束力的请求,所以如果你需要确定性,你会被 this old swap trick 卡住。 没错,这是一个非绑定请求。然而,OP 使用的 MSVC 2010 实现似乎确保了请求得到满足:msdn.microsoft.com/fr-fr/library/dd647619%28v=vs.100%29.aspx【参考方案2】:

这是因为擦除不会释放内存,它只是擦除元素。看看Herbs。

(真正)释放你可以做的记忆(来自参考链接):

“Shrink-To-Fit”向量或双端队列的正确方法

那么,我们是否可以编写代码来缩小向量“以适应”,使其容量刚好足以容纳包含的元素?明明reserve()不能胜任,幸好确实有办法:

vector<Customer>( c ).swap( c );
// ...now c.capacity() == c.size(), or
// perhaps a little more than c.size()

【讨论】:

不错的技巧:您创建一个临时未命名的空向量并进行交换。由于匿名临时在创建它们的语句结束时自动销毁,因此内存被释放。但是阅读起来有点棘手。与更容易理解的更简单的c=vector&lt;Customer&gt;(); 有性能差异吗? @Christophe:实际上,这个示例创建了一个向量的副本,然后将其与原始向量交换。希望副本的 capacity() 实际上比原始的少,并且值得暂时增加内存使用和元素复制。 对,它基本上是“修剪”本身:) @Blastfurnace 是的,但是由于之前删除的所有元素都将其修剪为空。这就是为什么我问分配一个空向量是否更简单。 @Christophe 这不是清空向量,而是释放向量在增长时(可能)保留的“额外”内存。看看参考,它应该解释这个成语:)【参考方案3】:

vector.ersase() 只保证从向量中删除元素,不保证减少底层数组的大小(因为该过程相当昂贵)。 IE:它只是清零数据,不一定释放它。

如果您需要一个仅与其包含的元素一样大的向量,请尝试使用vector.resize(vector.size())

【讨论】:

您的最后一个示例不会释放任何内存,resize() 具有相同或更小的size() 不会减少capacity() 感谢您的回复,是的,我同意您的看法,但为什么在很多情况下它会分配大约 1.5Mb 的内存(据说丢失且数量不固定)?我预计擦除后分配的内存差异为0。请参考问题的更新。【参考方案4】:

IIRC 在 Windows 上的 Debug 版本中,new 实际上是 #definedDEBUG_NEW,这会导致(除其他外)内存块未被实际释放,而只是标记为“已删除”。

发布版本是否有相同的行为?

【讨论】:

是的,不幸的是,我在发布版本中得到了相同的行为,它只分配了更少的内存(大约 50%)。【参考方案5】:

难题的一部分可能是 std::vector 无法从底层内存缓冲区中删除条目,如果它们不在缓冲区的末尾(你的不是),因此保留的条目被移动 - 可能到一个完全不同的缓冲区。由于您正在擦除第一个元素,因此允许 std::vector 分配一个 additional 缓冲区将剩余元素复制到,然后在复制后丢弃旧缓冲区。因此,您最终可能会同时使用两个缓冲区,并且您的内存管理器可能不会将丢弃的缓冲区返回给操作系统,而是保留内存以便在后续分配中重新使用它。这可以解释单个循环迭代的内存使用量增加。

【讨论】:

以上是关于Visual C++ 矢量擦除会增加内存使用量?的主要内容,如果未能解决你的问题,请参考以下文章

矢量擦除迭代器超出范围[关闭]

如何在不因内存不足而导致应用程序崩溃的情况下擦除矢量元素?

C++ STL 矢量模板如何将其对象存储在 Visual Studio 编译器实现中?

从向量中最快擦除元素或更好地使用内存(排序基数)

C++中erase函数的使用,可以用来删除内存擦除

如何在 Visual Studio 2015(用于 C++)中仅禁用 SIMD 自动矢量化优化?