【问题标题】:How does a memory leak improve performance内存泄漏如何提高性能
【发布时间】:2010-08-04 15:53:23
【问题描述】:

我正在构建一个充满节点的大型 RTree(空间索引)。它需要能够处理许多查询和更新。对象不断地被创建和销毁。我正在运行的基本测试是随着树中对象数量的增加查看树的性能。我以 100 为增量插入 100-20000 大小均匀、随机定位的对象。搜索和更新与我目前面临的问题无关。

现在,当存在 NO 内存泄漏时,“插入树”性能无处不在。它从大约 15000 个对象的 10.5 秒到大约 18000 个对象的 1.5 秒不等。没有任何模式。

当我故意添加泄漏时,就像输入“new int;”一样简单我没有将它分配给任何东西,它本身就有一条线,性能立即下降到一个很好的平缓曲线,从 100 个对象的 0(大约)秒到 20k 的 1.5 秒倾斜。

此时非常非常失落。如果你想要源代码,我可以包含它,但它是 huuugggeeee 并且实际上唯一不同的行是“new int;”

提前致谢! -尼克

【问题讨论】:

  • 您可能需要简化您的测试代码 - 发生了一些奇怪的事情,而且您可能看不到测试的复杂性。尝试使用更简单的数据结构运行类似的测试,然后从那里向上移动。
  • 在我看来,您的问题出在其他地方,性能与 new int; 之间的关系大多是偶然的。
  • 您确定new int; 幸免于难吗?
  • 是的。通过大会。
  • 您的速度下降了一个数量级?没有比简单地暂停它并查看它在做什么更容易查看问题所在的方法了。

标签: c++ performance memory-leaks


【解决方案1】:

我不确定您是如何想到这个new int 测试的,但这不是解决问题的好方法 :) 使用分析器运行您的代码并找出真正的延迟在哪里。然后集中精力修复热点。

g++ 已内置 - 只需使用 -pg 编译即可

【讨论】:

  • 所以我对 C++ 比较陌生,我下载了一个类来帮助我计时,我一直在尝试尽可能快地更新树(删除它是愚蠢的一个元素,然后再次添加它,如果你只是要在你已经在的节点内移动一点......)。我正在使用这门课来计时,一切都很顺利,直到我意识到我从来没有真正删除过。我删除了它,然后一切都崩溃了。
  • "proper" profiler 的工作方式有点不同。我不知道您使用的是哪个编译器,但它们都有一些可用的分析器。您的应用程序配置文件将包括您正在调用的所有函数的计时,而不是计时单个函数,以及由调用者函数(或堆栈跟踪)分解的基本统计信息。与其猜测,不如尝试分析实际运行并找出需要很长时间的原因。必须有一个合理的解释,并且您需要查明实际的慢行。 Profiler 不会修改代码本身,因此它们比计时类更安全。
【解决方案2】:

没有更多信息是不可能确定的。

但是我想知道这是否与堆碎片有关。通过创建释放许多内存块,您可能会创建整个负载的小块内存链接在一起。内存管理器需要跟踪它们,以便在需要时再次分配它们。

当你释放一个块时,一些内存管理器会尝试将它与周围的内存块“合并”,而在高度碎片化的堆上,这可能会非常慢,因为它试图找到周围的块。不仅如此,如果你的物理内存有限,它可以“触摸”许多物理内存页面,因为它遵循内存块链,这可能导致整个加载极慢的页面错误,这些页面错误的速度将非常可变,具体取决于操作系统决定为该进程提供多少物理内存。

通过留下一些未释放的内存,您将改变这种访问模式,这可能会对速度产生很大影响。例如,您可能会强制运行时库每次都分配新的内存块,而不必跟踪大小合适的现有块以进行重用。

我没有证据表明您的程序中存在这种情况,但我知道当执行大量新的和空闲的程序时,内存碎片通常是导致程序缓慢的原因。

【讨论】:

  • 这也可能是正在发生的事情。有没有办法可以包含图片?我对每一种可能的情况都有疯狂的图表
【解决方案3】:

解释这一点的可能发生的事情(理论)

  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
  • 我同意如果它是一个高达......假设运行时间的 25% 的变化。但是“约 15000 个对象的 10.5 秒与约 18000 个对象的 1.5 秒”相差 8 倍。我在紧密循环中看到最多 4 倍的差异,几乎专门测试了一次页面错误。所以虽然这可能是真正的解释,但代码中的错误更有可能是:)
【解决方案4】:

我在这里暗中尝试,但问题可能是堆碎片化的方式。你说你正在创建一个破坏性的大量对象。我将假设这些对象都是不同大小的。

当在堆上分配内存时,需要大小的单元会从堆中断开。当内存被释放时,单元被添加到一个空闲列表中。当执行新的分配时,分配器会遍历堆,直到找到足够大的单元。在进行大量分配时,空闲列表可能会变得相当长,并且遍历列表可能会花费大量时间。

现在 int 相当小。因此,当您执行新的 int 时,它很可能会吃掉空闲列表上的所有小堆单元,从而显着加快更大的分配速度。

但是,您可能会分配和释放类似大小的对象。如果您使用自己的空闲列表,您将保护自己的许多堆遍历,并可能显着提高性能。这正是 STL 分配器为提高性能所做的工作。

【讨论】:

    【解决方案5】:

    解决方案:不要从 Visual Studio 运行。实际运行 .exe 文件。想通了这一点,因为这就是分析器正在做的事情,并且数字神奇地下降了。检查内存使用情况和版本运行(并给我特别的时间)没有爆炸到过大的大小。

    为什么 Visual Studio 会做这样荒谬的废话的解决方案:没有线索。

    【讨论】:

    • 因为关心代码性能的程序员在测试性能时通常不会通过 IDE 运行它?
    • 如果附加调试器,性能会下降当然
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-12-22
    • 1970-01-01
    • 2014-03-09
    • 1970-01-01
    • 2023-03-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多