解除内存需要时间吗?

时间:2010-04-03 19:31:26

标签: c++ linux memory memory-management

我有一个C ++程序,在执行期间,将分配大约3-8Gb的内存来存储哈希表(我使用tr1 / unordered_map)和各种其他数据结构。

但是,在执行结束时,在返回shell之前会有一段很长的停顿。

例如,在我的主要功能的结束我有

std::cout << "End of execution" << endl;

但我的程序的执行将类似于

  

$ ./program
  做什么...
  执行结束
   [长时间停顿2分钟]
  $ - 返回shell

这是预期的行为还是我做错了什么?

我猜这个程序最终会释放内存。但是,当您关闭应用程序时,使用大量内存的商业应用程序(如photoshop)不会出现此暂停。

请告知:)

修改:最大的数据结构是unordered_map,其中包含string并存储list integers

我在linux上使用g++ -O2,我使用的计算机有128GB的内存(其中大部分是免费的)。有一些巨型物体

解决方案:我最终摆脱了哈希表,因为它几乎已经满了。这解决了我的问题。

15 个答案:

答案 0 :(得分:13)

如果程序完成后数据结构足够复杂,那么释放它们实际上可能需要很长时间。

如果你的程序实际上必须创建这样复杂的结构(做一些内存分析以确保),那么可能没有干净的方式

可以通过脏黑客来释放内存 - 至少在进程终止时自动释放进程分配的所有内存的操作系统上

您可以通过直接调用libc的exit(3)函数或操作系统的_exit(2)来实现。但是,我会非常小心关于验证这不会使某些C ++析构函数代码可能正在执行的任何其他(重要)清理短路。这个或不做的是高度依赖系统的(操作系统,编译器,libc,你正在使用的API,......)。

答案 1 :(得分:8)

是的,内存的重新分配可能需要一些时间,而且可能还有代码执行,例如调用析构函数。 Photoshop不使用3-8GB内存。

此外,您应该向应用程序添加分析,以确认它是内存的释放而不是其他内容。

答案 2 :(得分:7)

(我把它作为对ndim的回复开始,但它得很长)

正如ndim已经发布的那样,终止可能需要很长时间 可能的原因是:

  • 你有很多分配,并且堆的一部分被交换到磁盘。
  • 长期运行的析构函数
  • 其他atexit例程
  • 特定于操作系统的清理,例如通知DLL的线程&amp; Windows上的进程终止(不知道Linux上到底发生了什么。)

exit不是这里最糟糕的解决方法,但实际行为取决于系统。例如WIndows / MSVC上的exit CRT将运行全局析构函数/ atexit例程,然后调用ExitProcess来执行关闭句柄(但不一定要刷新它们 - 至少不保证)。

下行:堆分配对象的析构函数不会运行 - 如果你依赖它们(例如保存状态),你就是吐司。此外,追踪真正的内存泄漏变得更加困难。

查明原因您应首先分析发生的情况。

e.g。通过手动释放仍分配的根对象,可以将释放时间与其他进程清理分开。记忆是可能的原因与你的描述一致,但它不是唯一可能的。一些清理代码在运行到超时之前也会死锁。监控统计信息(例如CPU /交换活动/磁盘使用)可以提供线索。

检查发布版本 - 调试版本通常使用堆上的额外数据,这会极大地增加清理成本。

不同的分配器
如果分配是问题,那么使用自定义分配机制可能会从中受益匪浅。示例:如果您的地图仅增长(项目永远不会删除),arena allocator可以提供很多帮助。如果您的整数列表有很多节点,请切换到vector,或者如果需要随机插入,请使用绳索。

答案 3 :(得分:6)

当然可以。

大约7年前,我在一个项目上遇到了类似的问题,内存要少得多,但我认为计算机速度也慢了。

我们最后必须免费查看汇编语言以找出它为何如此缓慢而且它似乎基本上将释放的块保存在链表中以便它们可以重新分配并且还扫描该列表寻找组合块。扫描列表是一个O(n)操作,但释放'n'对象将其转换为O(n ^ 2)

我们的测试数据大约需要5秒钟才能释放内存,但有些客户的数据大约是我们每次使用的数据的10倍,并且需要5-10分钟来关闭系统中的程序。

我们修复了它,正如刚刚终止该过程所建议的那样,并让操作系统清理掉混乱(我们知道在我们的应用程序上可以安全地做到这一点)。

也许你有一个比我们几年前更加明智的免费功能,但我只是想发布,如果你有许多对象可以免费和O(n)免费操作,这是完全可能的

答案 4 :(得分:5)

我无法想象你如何使用足够的内存来解决它,但我加快程序的一种方法是使用boost::object_pool为二叉树分配内存。对我来说主要的好处是我可以把对象池作为树的成员变量,当树超出范围或被删除时,对象池将被一次性删除(让我不必使用)节点的递归解构器)。 object_pool会在退出时调用其所有对象解构器。我不确定它是否以特殊方式处理空的解构器。

如果你不需要你的分配器来调用构造函数,你也可以使用boost::pool,我认为可以更快地解除分配,因为它根本不需要调用解构器而只是删除了内存块在一个free()

答案 5 :(得分:4)

释放内存可能需要一些时间 - 数据结构正在更新。多长时间取决于使用的分配器。

此外,可能还有不仅仅是内存释放 - 如果正在执行析构函数,可能还会有更多内容。

2分钟听起来好像很多时间 - 您可能想要在调试器中逐步完成清理代码(或者如果更方便的话,使用分析器)来查看实际上一直在使用的内容。

答案 6 :(得分:4)

时间可能并非完全浪费了释放内存,而是调用了所有的析构函数。您可以提供自己的不调用析构函数的分配器(如果映射中的对象不需要被破坏,只需要解除分配)。

另请查看另一个问题:C++ STL-conforming Allocators

答案 7 :(得分:3)

通常,将内存释放为进程结束时不会将其作为进程的一部分,而是作为操作系统清理功能。您可以尝试使用valgrind之类的东西来确保正确处理您的记忆。但是,编译器还会执行某些操作来设置和拆除您的程序,因此进行某种性能分析,或者使用调试器来逐步完成拆解时的操作可能会很有用。

答案 8 :(得分:2)

当你的程序退出时,调用所有全局对象的析构函数。 如果其中一个需要很长时间,你会看到这种行为。

寻找全局对象并研究它们的析构函数。

答案 9 :(得分:1)

抱歉,这是一个可怕的问题。您需要显示显示您正在使用的特定算法和数据结构的源代码。

这可能是取消分配,但这只是一个疯狂的猜测。你的破坏者在做什么?也许是分页像疯了似的。仅仅因为你的应用程序分配了X量的内存,这并不意味着它会得到它。最有可能的是它会分页虚拟内存。根据应用程序和操作系统的具体情况,您可能会遇到很多页面错误。

在这种情况下,在后台运行iostat和vmstat可能会有所帮助,看看到底发生了什么。如果您看到很多I / O,那么肯定会出现页面错误。 I / O操作总是比内存操作更昂贵。

如果最后所有失效的时间完全是由于取消分配,我会感到非常惊讶。

收到“结束”消息后立即运行vmstat和iostat,并查找I / O转香蕉的任何迹象。

答案 10 :(得分:1)

内存中的对象组织在堆中。它们不会立即删除,它们会逐个删除,删除对象的成本为O(log n)。释放它们需要很长时间。

答案是,是的,需要花费很多时间。

答案 11 :(得分:1)

您可以使用析构函数调用free而不是my_object->~my_class()来避免在对象上调用delete my_object。您可以通过覆盖类中的free来取消operator delete( void * ) {},从而避免对类的所有对象进行delete。具有虚拟析构函数的派生类将继承using base::operator delete;,否则您可以复制粘贴(或者exit)。

这比调用{{1}}要干净得多。请确保你不需要那个记忆!

答案 12 :(得分:1)

我猜你的无序映射是一个全局变量,在进程启动时调用其构造函数,并在进程退出时调用析构函数。

你怎么知道地图是否有罪?

你可以通过分配一个新的来测试你的unordered_map是否负责(并且我猜它是这样的),好吧,嗯...忘了删除它。

如果您的流程退出速度更快,那么您就有罪魁祸首。

为什么这么懒散?

现在,只需阅读你的帖子,对于你的无序地图,我看到了潜在的分配:

  • 字符串已分配缓冲区
  • 列出项目(每个项目都是字符串+其他内容)
  • 无序地图项+铲斗阵列

如果此无序地图中有3-8 Gb的数据,则表示上面的每个项目都需要某种新的和删除。如果你逐一释放每件物品,可能需要时间。

其他原因?

请注意,如果您在流程执行过程中逐项向地图项添加项目,new并不完全可感知......但是当您要清除所有项目时,所有已分配的项目必须在同时,这可以解释建筑/使用和破坏之间的感知差异......

现在,析构函数可能需要时间,原因还有其他原因。

例如,在调试模式下的Visual C ++ 2008上,例如,在销毁STL迭代器时,析构函数会验证迭代器是否仍然正确。这导致我的对象破坏(基本上是一个节点树,每个节点都有子节点列表,到处都是迭代器)的速度相当慢。

你正在研究gcc,所以也许他们有自己的调试测试,或者你的析构函数正在做额外的工作(例如记录?)......

答案 13 :(得分:0)

根据我的经验,免费或删除的电话不应占用大量时间。也就是说,我已经看到很多情况下,由于析构函数执行了非常重要的事情,它确实需要非常重要的时间来破坏对象。如果您无法分辨销毁过程中需要花费的时间,请使用调试器和/或分析器来确定发生了什么。如果探查器向您显示它确实是对free()的调用需要花费很多时间,那么您应该改进内存分配方案,因为您必须创建大量的小对象。

正如您所指出的,大量应用程序会分配大量内存,并且在关机期间不会产生大量内存,因此您的程序无法做到这一点。

答案 14 :(得分:0)

我建议(正如其他人一样)一个简单的强制进程终止,如果你确定除了空闲内存之外没什么可做的(例如,没有文件i / o等等)。

问题在于,当你释放内存时,通常它并没有真正返回到操作系统 - 它被保存在一个列表中以便重新分配,这显然很慢。但是,如果您终止进程,操作系统将立即回收所有内存,这应该大大加快。但是,正如其他人所说,如果你有任何需要运行的析构函数,你应该确保它们在强制调用exit()或ExitProcess或anysuch函数之前运行。

你应该知道的是,解除分配的内存(例如,地图中的两个节点)由于缓存效应比在向量中释放内存要慢得多,因为CPU需要访问内存以释放它运行任何析构函数。如果你释放了大量非常分散的内存,你可能会对此产生影响,应该考虑改为更加连续的结构。

我实际上有一个问题,分配内存比取消分配更快,并且在分配内存然后取消分配后,我有内存泄漏。最终,我发现这就是原因。