内存密集型应用中的内存管理

时间:2009-01-23 18:29:35

标签: c++ windows optimization memory-management

如果您在Windows上使用C ++开发内存密集型应用程序,您是否选择编写自己的自定义内存管理器以从虚拟地址空间分配内存,或者允许CRT控制并为您执行内存管理?我特别关注堆上小对象的分配和释放造成的碎片。因此,我认为只要有足够的内存但是碎片化,这个过程就会耗尽内存。

9 个答案:

答案 0 :(得分:38)

我认为最好的办法是在配置文件证明之前不要实施CRT,因为CRT会以损害应用程序性能的方式分割内存。 CRT,核心操作系统和STL人员花了很多时间考虑内存管理。

您的代码很可能在现有分配器下执行得非常好,无需进行任何更改。肯定有更好的机会,而不是你第一次获得内存分配器。我之前已经为类似情况编写了内存分配器,这是一项艰巨的任务。不那么令人惊讶的是,我继承的版本充斥着碎片问题。

等到配置文件显示出问题的另一个好处是,您还将知道您是否确实修复过任何问题。这是性能修复中最重要的部分。

只要您使用标准集合类和算法(例如STL / BOOST),就不应该很难在周期中插入新的分配器来修复代码库的部分内容。需要修复。您不太可能需要为整个程序使用手动编码分配器。

答案 1 :(得分:4)

虽然大多数人都表示你不应该编写自己的内存管理器,但如果出现以下情况仍然有用:

  • 您有特定的要求或情况,您确信可以编写更快的版本
  • 你想写自己的内存覆盖逻辑(以帮助调试)
  • 您想要跟踪内存泄漏的地方

如果您想编写自己的内存管理器,请务必将其拆分为以下4个部分:

  1. “拦截”对malloc / free(C)和new / delete(C ++)的调用的部分。这对于new / delete(只是全局new和delete运算符)来说非常容易,但对于malloc / free也是如此('覆盖'CRT的功能,重新定义对malloc / free的调用,......)
  2. 表示内存管理器入口点的部分,由“拦截器”部分调用
  3. 实现实际内存管理器的部分。可能你会有多个这样的实现(取决于具体情况)
  4. 用“调用堆栈”信息“装饰”已分配内存的部分,覆盖区域(又名红色区域),......
  5. 如果这四个部分明显分开,也可以很容易地将一个部件替换为另一个部件,或者添加一个新部件,例如:

    • 添加英特尔Tread Building Blocks库的内存管理器实现(至第3部分)
    • 修改第1部分以支持新版本的编译器,新平台或全新的编译器

    自己编写了一个内存管理器,我只能说明一个简单的扩展自己的内存管理器的方法真的很方便。 例如。我经常要做的是在长时间运行的服务器应用程序中发现内存泄漏。有了我自己的记忆管理员,我这样做:

    • 启动应用程序,让它“热身”一段时间
    • 请您自己的内存管理器转储已用内存的概述,包括通话时的调用堆栈
    • 继续运行该应用程序
    • 进行第二次转储
    • 在调用堆栈上按字母顺序对两个转储进行排序
    • 查找差异

    虽然你可以用开箱即用的组件做类似的事情,但它们往往有一些缺点:

    • 通常会严重降低应用程序的速度
    • 通常他们只能在应用程序结束时报告泄漏,而不是在应用程序运行时报告

    但是,也要尝试现实:如果你没有内存碎片,性能,内存泄漏或内存覆盖的问题,那么编写你自己的内存管理器是没有理由的。

答案 2 :(得分:2)

曾经有很好的第三方插件堆替换库用于VC ++,但我不记得这个名字了。当我们开始使用它时,我们的应用程序加速了30%。

编辑:这是SmartHeap - 谢谢,ChrisW

答案 3 :(得分:2)

是MicroQuill的SmartHeap吗?

答案 4 :(得分:2)

根据我的经验,当您不断分配和释放大缓冲区(例如超过16k)时,碎片主要是一个问题,因为那些最终将导致内存不足的碎片,如果堆找不到其中一个足够大的地方。

在这种情况下,只有那些对象应该有特殊的内存管理,其余部分保持简单。如果它们总是具有相同的大小,则可以使用缓冲区重用,如果它们的大小不同,则可以使用更复杂的内存池。

默认堆实现在以前的分配之间为较小的缓冲区找到一些位置应该没有任何问题。

答案 5 :(得分:1)

  

您选择编写自己的自定义内存管理器以从虚拟地址空间分配内存,还是允许CRT控制并为您执行内存管理?

标准库通常足够好。如果不是,那么不是替换它,而是为特定的类覆盖operator newoperator delete,而不是替换所有类。

答案 6 :(得分:1)

这在很大程度上取决于你的内存分配模式。根据我的个人经验,项目中通常有一两个类在内存管理方面需要特别考虑,因为它们经常用在您花费大量时间的代码部分。可能还有一些类在某些特定情况下需要特殊处理,但在其他情况下可以使用而不必费心。

我经常最终在std :: vector或类似和显式的东西中管理那些类型的对象,而不是覆盖类的分配例程。在许多情况下,堆真的是过度杀戮,分配模式是如此可预测,以至于您不需要在堆上进行分配,而是在一些更简单的结构中,从堆中分配较大的页面,这些页面的记账开销比分配每个实例上的更少。堆。

这些是一些需要考虑的一般事项:

首先,快速分配和销毁的小对象应该放在堆栈上。最快的分配是从未完成的分配。堆栈分配也是在没有任何全局堆锁定的情况下完成的,这对多线程代码是有益的。与像Java这样的GC语言相比,在c / c ++中分配堆可能相对昂贵,所以除非你需要,否则尽量避免使用它。

如果你做了很多分配,你应该小心线程性能。一个经典的缺陷是字符串类,它往往会对用户隐藏很多分配。如果你在多个线程中进行大量的字符串处理,他们最终可能会在堆代码中争论一个互斥锁。为此,控制内存管理可以加速很多事情。切换到另一个堆实现通常不是解决方案,因为堆仍然是全局的,并且您的线程将对此进行争论。我认为google在多线程环境中的堆应该更快。我自己没试过。

答案 7 :(得分:0)

不,我不会。

我有机会写出一个更好的代码然后CRT与谁知道在其中投入了多少数百人的年轻人。

我会搜索一个专门的库,而不是重新发明轮子。

答案 8 :(得分:0)

某些开源软件(如doxygen)使用的解决方案,其目的是在超过特定内存量时将一些实例存储到文件中。在您需要时从文件中获取数据后。