我有一个C ++程序,在执行过程中,将分配大约3-8Gb的内存来存储哈希表(我使用tr1 / unordered_map)和各种其他数据结构。
但是,在执行结束时,在返回到shell之前会有很长的停顿。
例如,在我的主要function的最后
std::cout << "End of execution" << endl;
但是我的程序的执行将会像这样
$ ./程序
做东西…
执行结束
[也许2分钟的暂停]
$ – 返回到shell
这是预期的行为还是我做错了什么?
我猜这个程序最后是在释放内存。 但是,当您closures应用程序时,使用大量内存的商业应用程序(如photoshop)不会出现这种暂停。
请指教 :)
编辑:最大的数据结构是一个用string
键入的unordered_map
并存储一个integers
list
。
我在Linux上使用g++ -O2
,我使用的计算机有128GB的内存(大部分是免费的)。 有几个巨大的物体
解决scheme:我最终摆脱了哈希表,因为它几乎已经满了。 这解决了我的问题。
如果程序结束时数据结构非常复杂,释放它们可能需要很长时间。
如果你的程序实际上必须创建这样复杂的结构(做一些内存分析来确保),那么可能没有干净的方法 。
你可以通过肮脏的黑客来简化内存释放 – 至少在那些操作系统上,进程分配的所有内存在进程终止时自动释放。
你可以直接调用libc的exit(3)
函数或者操作系统的_exit(2)
。 但是,我会非常小心地核实这不会使C ++析构函数代码可能正在执行的其他(重要)清理操作短路。 而这些做什么或不做什么是高度依赖于系统(操作系统,编译器,libc,你正在使用的API,…)。
是的,释放内存可能需要一些时间,也可能有代码执行像被调用的析构函数。 Photoshop不使用3-8GB的内存。
另外你也许应该添加分析到你的应用程序来确认它是内存的释放,而不是别的。
(我是以ndim的回复开始的,但是很长)
正如ndim已经发布的,终止可能需要很长时间。
可能的原因是:
atexit
例程 exit
并不是这里最糟糕的解决方法,但是,实际行为依赖于系统。 例如在WIndows / MSVC上exit
CRT将运行全局析构函数/ atexit
例程,然后调用ExitProcess来关闭句柄(但不一定需要刷新它们 – 至少不能保证)。
缺点:堆分配对象的析构函数不运行 – 如果你依赖它们(例如保存状态),你就是烤面包。 另外,追踪真实内存泄漏也变得困难得多。
找到原因您应该首先分析正在发生的事情。
例如通过手动释放仍分配的根对象,可以将解除分配时间与其他进程清理分开。 记忆是可能的原因,符合你的描述,但它不是唯一可能的原因。 在运行到超时之前,某些清理代码也可能死锁。 监视统计信息(如CPU /交换活动/磁盘使用)可以提供线索。
检查发布版本 – 调试版本通常在堆上使用额外的数据,这会极大地增加清理成本。
不同的分配器
如果分配是问题,那么使用自定义分配机制可能会使您受益匪浅。 例如:如果你的地图只增长(项目从不删除), 竞技场分配器可以帮助很多。 如果您的整数列表有许多节点,请切换到vector
,或者如果您需要随机插入,请使用绳索。
当然有可能。
大约7年前,我在一个项目上遇到了类似的问题,但是内存要少得多,但是电脑的速度也是我想的。
最后,我们必须免费看看汇编语言,以便弄清楚它为什么如此缓慢,它似乎基本上将释放的块保存在一个链表中,以便它们可以被重新分配,并且也在扫描该列表寻找块结合。 扫描列表是O(n)操作,但释放“n”个对象将其变为O(n ^ 2)
我们的测试数据花费了大约5秒的时间来释放内存,但是一些客户的数据量是我们每次使用的数据量的10倍左右,并且需要5-10分钟关闭系统上的程序。
我们修正了这个问题,就像刚才终止这个过程所暗示的那样,让操作系统清理了这个混乱(我们知道这对我们的应用程序来说是安全的)。
也许你有一个更合理的免费功能,我们几年前,但我只是想发布,如果你有很多对象免费和O(n)自由操作是完全可能的。
我无法想象如何使用足够的内存来处理它,但是加速程序的一个方法是使用boost::object_pool
为二叉树分配内存。 对我来说主要的好处是我可以把对象池当作树的成员变量,当树超出作用域或被删除时,对象池将被一次性删除(让我不必使用节点的递归解构器)。 object_pool
确实在退出时调用了所有的对象解构器。 我不确定它是否以特殊方式处理空的解构器。
如果你不需要你的分配器来调用一个构造函数,你也可以使用boost::pool
,我认为它可以更快地释放,因为它不必调用解构器,只需要在一个free()
删除内存块free()
。
释放内存可能需要时间 – 数据结构正在更新。 多少时间取决于正在使用的分配器。
此外,可能不仅仅是内存释放正在进行 – 如果正在执行析构函数,可能会比正在执行的更多。
2分钟听起来像很多时间 – 但你可能想要在调试器中逐步清理代码(或者如果更方便的话,使用分析器)来查看实际上一直在使用的内容。
时间可能不会完全被浪费,而是调用所有的析构函数。 你可以提供你自己的分配器,它不调用析构函数(如果地图中的对象不需要被破坏,但只能被释放)。
还要看看另一个问题: 符合C ++ STL的分配器
通常情况下,作为一个进程的内存释放不被视为进程的一部分,而是作为一个操作系统清理功能。 你可以尝试像valgrind的东西,以确保你的记忆正在处理。 然而,编译器也会做一些事情来设置和拆卸你的程序,所以某种性能分析,或者使用调试器来分解在拆解时发生的事情可能是有用的。
当你的程序退出时,调用所有全局对象的析构函数。 如果其中一个需要很长时间,你会看到这种行为。
寻找全局对象并调查它们的析构函数。
对不起,但这是一个可怕的问题。 您需要显示显示您正在使用的特定算法和数据结构的源代码。
这可能是分配,但这只是一个疯狂的猜测。 你的破坏者在做什么? 也许是疯狂的分页。 只是因为你的应用程序分配了X的内存量,这并不意味着它会得到它。 最有可能的是将虚拟内存分页。 根据您的应用程序和操作系统的具体情况,您可能会遇到很多页面错误。
在这种情况下,可能会帮助在后台运行iostat和vmstat以查看发生了什么事情。 如果你看到很多的I / O,这是一个肯定的迹象,你是页面错误。 I / O操作将永远比内存操作更昂贵。
如果最后所有的失败时间纯粹是由于解除分配,我会感到非常惊讶。
运行vmstat和iostat,只要您收到“结束”消息,并寻找I / O进入香蕉的任何迹象。
内存中的对象被组织在一个堆中。 它们不是一次删除,而是逐个删除,删除对象的代价是O(log n)。 释放他们需要loooong。
答案是,是的,这需要很多时间。
你可以通过使用析构函数调用my_object->~my_class()
而不是delete my_object
来避免free
被调用。 您可以通过在类中覆盖和取消operator delete( void * ) {}
来避免free
所有对象的类。 具有虚拟析构函数的派生类将继承该delete
,否则可以复制粘贴(或者可能using base::operator delete;
)。
这比调用exit
要干净得多。 只要确定你不需要回忆
我猜你的无序映射是一个全局变量,其构造函数在进程启动时被调用,析构函数在进程退出时被调用。
你可以测试你的unordered_map是否有责任(我想是的),通过分配一个新的,以及,啊哈……忘记删除它。
如果你的流程退出的速度更快,那么你有罪魁祸首。
现在,通过阅读您的文章,为您的无序地图,我看到潜在的分配:
如果你在这个无序的地图上有3-8Gb的数据,这意味着上面的每个项目都需要某种新的和删除。 如果你逐一解放所有项目,可能需要时间。
请注意,如果您在流程执行时逐项向地图添加项目, new
项目并不完全可察觉…但是当您要清理所有项目时,所有已分配的项目必须同时销毁,这可以解释建设/使用和销毁之间的差异
现在,破坏者可能需要一段时间才能获得额外的理由。
例如,在调试模式下的Visual C ++ 2008中,例如,在销毁STL迭代器时,析构函数验证迭代器是否仍然正确。 这导致我的对象被破坏(这基本上是一个节点树,每个节点都有一个子节点列表,每个地方都有迭代器),导致相当缓慢。
你正在使用gcc,所以也许他们有自己的调试测试,或者你的析构函数正在做额外的工作(例如日志?)…
根据我的经验,免费或删除电话不应占用大量时间。 也就是说,我已经看到很多情况下,因为析构函数做了一些不重要的事情,不得不花费很多时间来破坏对象。 如果在销毁过程中无法确定需要花费什么时间,请使用调试器和/或分析器来确定发生了什么事情。 如果事件探查器显示它确实是调用free()会花费很多时间,那么应该改进内存分配方案,因为您必须创建大量的小对象。
正如你注意到大量的应用程序分配大量的内存,并在关机过程中没有明显的内存,所以没有理由你的程序不能做同样的事情。
如果你确定你没有什么可做的,但是可用的内存(例如,没有文件I / O等等),我会建议(像其他人一样)一个简单的强制进程终止。
问题是当你释放内存的时候,通常它并没有返回到操作系统 – 它被保存在一个被重新分配的列表中,这显然很慢。 但是,如果您终止进程,操作系统将立即回收您的所有内存,这应该快得多。 但是,正如别人所说,如果你有任何需要运行的析构函数,你应该确保它们在强制调用exit()或ExitProcess或者其他函数之前运行。
你应该知道的是,由于缓存效应而释放分配的内存(例如,映射中的两个节点)要比释放向量中的内存慢得多,因为CPU需要访问内存以释放内存并运行任何析构函数。 如果你释放了非常大量的非常多的内存,那么你可能会遇到这种情况,应该考虑改变一些更加连续的结构。
我实际上遇到了一个问题:分配内存比分配内存要快,在分配内存然后解除分配之后,我有内存泄漏。 最终,我发现这就是为什么。