内存泄漏如何提高性能

4
我正在构建一个大型的RTree(空间索引),它需要能够处理许多查询和更新。对象不断地被创建和销毁。我运行的基本测试是查看树的性能随着树中对象数量的增加而变化。我以100-20000个均匀大小、随机位置的对象为增量插入。搜索和更新与我目前面临的问题无关。
现在,当没有内存泄漏时,“插入到树”性能表现得很随意。它从大约15000个对象的10.5秒到18000个对象的1.5秒不等。没有任何模式。
当我故意添加一个泄漏,简单地放入“new int;”并不将其分配给任何东西,这就是一行代码,性能立即下降到一个漂亮的平滑曲线,从100个对象的0秒斜坡到完整的20k的1.5秒。
此时非常迷茫。如果您想要源代码,我可以包含它,但它非常庞大,唯一有影响的是“new int;”。
提前感谢! -nick

3
你可能需要简化你的测试代码 - 当前存在一些奇怪的问题,但由于测试的复杂性,你可能看不到它。尝试使用一个更简单的数据结构运行类似的测试,然后逐步提升难度。 - Michael Kohne
4
听起来你的问题可能出在别处,而性能和new int;之间的关系大多是偶然的。 - Jerry Coffin
你确定 new int; 经过了优化而幸存下来了吗? - pmr
没错。已经查看了汇编代码。 - nickneedsaname
你遇到了数量级的减速吗? 暂停程序并查看正在执行的操作是找出问题的最简单方法。 - Mike Dunlavey
这真是太有趣了,我迫不及待地想看到一些源代码。 :D - Razor Storm
5个回答

2

我不确定你是怎么想出这个new int测试的,但这不是修复问题的很好的方式 :) 使用分析器运行代码,找出真正的延迟所在。然后集中精力解决热点问题。

g++内置了它 - 只需使用-pg编译即可。


所以我对C++相对较新,我下载了一个类来帮助我计时,我一直在尝试尽可能快地更新树(如果您只是要在已经存在的节点中移动一点点,那么删除元素然后再添加它就很愚蠢...)。我使用这个类来计时,一切都进行得很顺利,直到我意识到我从未真正删除过。我删除了它,然后一切都乱了套。 - nickneedsaname
“正确的”分析器工作方式有些不同。我不知道您使用的编译器是哪个,但它们都有一些可用的分析器。与计时单个函数不同,您的应用程序配置文件将包括您调用的所有函数的时间,基本统计数据按调用者函数(或堆栈跟踪)进行了分解。请尝试对实际运行进行分析,找出需要很长时间的原因。必须有合理的解释,并且您需要确定实际缓慢的代码行。分析器不会修改代码本身,因此比计时类更安全。 - viraptor

2
没有更多的信息,很难确定。
但是我想这可能与堆碎片有关。通过创建和释放许多内存块,您很可能会创建一大堆小的内存碎片并将它们链接在一起。内存管理器需要跟踪它们,以便在需要时重新分配它们。
当您释放一个内存块时,某些内存管理器会尝试将其与周围的内存块合并,在高度碎片化的堆上,这可能非常缓慢,因为它试图找到周围的内存块。不仅如此,如果您的物理内存有限,它可能会“触及”许多物理内存页面,因为它遵循内存块链,这可能会导致大量极其缓慢的页面故障,速度取决于操作系统决定为该进程提供多少物理内存。
通过保留一些未释放的内存,您将改变对内存的访问模式,这可能会对速度产生很大影响。例如,您可能会强制运行时库每次分配新的内存块,而不必跟踪适当大小的现有块以重用。
我没有证据表明这在您的程序中是这种情况,但我知道在执行大量新建和释放操作时,内存碎片通常是程序变慢的原因之一。

这也可能是正在发生的事情。有没有办法我可以包含图片?我有各种各样的图表,展示每种可能的情况。 - nickneedsaname

0

可能发生的事情,解释了这个问题(一种理论)

  1. 编译器没有删除空的新int
  2. 新的int在其中一个内部循环或递归遍历的某个地方,它被执行最多次
  3. 进程的总RSS增加,最终使用的进程总内存也随之增加
  4. 由此引起页面错误
  5. 由于页面错误,进程变成I/O绑定而不是CPU绑定,最终导致吞吐量下降。如果您能提及所使用的编译器和用于构建代码的编译器选项,将有助于解决这个问题。

这是我和我谈过的人所倾向的。Visual Studio 2005编译器。所有选项: /O2 /GL /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /FD /EHsc /MD /fp:fast /Yu"stdafx.h" /Fp"Release\DestinyTest.pch" /Fo"Release\" /Fd"Release\vc80.pdb" /W3 /nologo /c /Wp64 /Zi /TP /errorReport:prompt - nickneedsaname
如果运行时间的变化在...比如说25%以内,我会同意这个观点。但是“15000个对象用10.5秒,而18000个对象只用1.5秒”这种情况下,差别高达8倍。我曾经在一个紧密循环中测试页面错误时看到过最多4倍的差异。因此,虽然这可能是真正的解释,但代码中的错误更有可能是原因 :) - viraptor

0
我在这里猜测一个可能性,问题可能出在堆内存的碎片化上。您说您正在创建和销毁大量对象。我假设这些对象都是不同大小的。
在堆上分配内存时,所需大小的单元会从堆中分离出来。当释放内存时,该单元将添加到freelist中。当进行新的分配时,分配器会遍历堆,直到找到足够大的单元。在进行大量分配时,freelist可能会变得相当长,并且遍历该列表可能需要较长时间。
现在,int相当小。因此,当您使用new int时,它可能会消耗所有freelist上的小堆单元,从而显着加速更大的分配。
然而,很有可能您正在分配和释放类似大小的对象。如果您使用自己的freelists,您将节省许多堆遍历时间,并可以显着提高性能。这正是STL分配器为提高性能所做的事情。

-1
解决方案:不要从Visual Studio运行。实际上运行.exe文件。发现这一点是因为分析器正在做的事情,数字神奇地下降了。检查内存使用情况和运行版本(并给我异常时间)的解决方案没有爆炸到过度巨大的大小。
为什么Visual Studio会做这种荒谬的事情的解决方案:毫无头绪。

因为关心代码性能的程序员通常在测试性能时不会通过IDE运行它? - Bill
如果您附加调试器,性能肯定会下降。 - Axel Gneiting

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接