2009-01-23 149 views
7

如果您在Windows上使用C++开发内存密集型应用程序,您是否选择编写自己的自定义内存管理器来从虚拟地址空间分配内存,还是允许CRT控制并执行为你的内存管理?我特别担心由堆上的小对象的分配和释放造成的碎片化。正因为如此,我认为尽管存储器足够多但支离破碎,但过程将耗尽内存。内存密集型应用程序中的内存管理

回答

38

我认为最好的办法是不实现一个直到型材证明的CRT以这样的方式分段存储,损害你的应用程序的性能。 CRT,核心操作系统和STL人花费大量时间思考内存管理。

很有可能您的代码在现有分配器下执行得相当好,而且无需进行任何更改。当然,这比第一次获得内存分配器的机会要好。在类似的情况下,我已经写过内存分配器了,这是一项艰巨的任务。不太奇怪,我继承的版本充斥着碎片问题。

等待一个配置文件显示它是一个问题的另一个好处是,你也会知道你是否已经修复了任何东西。这是性能修复中最重要的部分。

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

+6

我希望我可以为此答案投票100次。我曾经和那些相信他们可以比那些有全职工作来完成这些工作的程序员更好地优化代码的人一起工作。最重要的是,他们永远不会测试他们的任何“优化”。 – 2009-01-23 19:27:24

+0

我也很遗憾,我不能满意地说出每一个优点。 – 2009-01-23 19:57:12

+0

但是有时候会出现这样一种罕见的情况,你确实需要这种自定义分配器来聚合所有内容并将固定块分块。我参与过多个项目,其中可用性(或缺乏)意味着成功与失业之间的差异。 – Crashworks 2009-01-23 20:18:19

2

曾经有优秀的VC++第三方嵌入式堆替换库,但我不记得名字了。我们的应用在开始使用时速度提高了30%。

编辑:这是SmartHeap - 感谢,ChrisW

+1

来自MicroQuill的SmartHeap – ChrisW 2009-01-23 18:35:43

0

不,我不会。

我写了一个更好的代码的机会,然后与谁知道多少数人的一年投资它的CRT是渺茫。

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

1

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

标准库通常足够好。如果不是这样,而不是取代它,更小的一步是覆盖operator newoperator delete为特定的类,而不是所有的类。

1

这很大程度上取决于您的内存分配模式。从我个人的经验来看,一个项目中通常有一到两个类需要特别考虑,因为它们在代码中花费大量时间的部分经常使用。也可能有一些类在某些特定的情况下需要特殊的处理,但是在其他情况下可以使用而不会打扰它。

我经常最终管理std :: vector中的那些类型的对象或类似的东西,而不是重写类的分配例程。在许多情况下,堆确实是过度杀伤性的,分配模式是如此可预测的,以至于你不需要在堆上分配,但是在一些更简单的结构中,从堆中分配更大的页面,其具有比分配每个单个实例少的簿记开销堆。

这些都是要考虑的一些一般的东西:这是分配和销毁应该迅速在堆栈上放

首先,小物件。最快的分配是那些从未完成的分配。堆栈分配也是在没有任何锁定全局堆的情况下完成的,这对于多线程代码是很有用的。在GC/C++中分配堆可能比Java等GC语言相对昂贵,所以尽量避免它,除非你需要它。

如果你做了很多分配,你应该小心线程性能。一个典型的缺陷是字符串类倾向于向用户隐藏大量的分配。如果你在多个线程中进行大量的字符串处理,他们可能会在堆代码中争取一个互斥体。为此,控制内存管理可以加快速度。切换到另一个堆实现通常不是这里的解决方案,因为堆仍然是全局的,并且您的线程将为此而战。我认为谷歌有一堆应该在多线程环境中更快。还没有尝试过自己。

2

从我的经验,碎片大多是当你不断地分配和释放较大的缓冲区(比如超过16K),因为那些最终将导致内存不足的那些,如果堆不能找到一个大问题其中一个足够的地方。

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

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

4

虽然大多数表明您不应该写自己的内存管理器,它可能仍然是有用的,如果:

  • 你必须在你确信你可以写一个更快的特定需求或情况版本
  • 你想给你写自己的记忆重写逻辑
  • 要跟踪其中的内存泄漏

的地方。如果你想为w(在调试帮助)成为你自己的内存管理器,重要的是将它分成以下4个部分:

  1. “拦截”对malloc/free(C)和new/delete(C++)的调用的一部分。这对于new/delete(只是全局的new和delete操作符)是很容易的,但对于malloc/free也是可能的('覆盖'CRT的功能,重新定义malloc/free的调用,...)
  2. 一个表示你的内存管理器入口点的部分,它由'拦截器'部分调用,它是实现内存管理器的一部分。也许你会对此有多种实现(视情况而定)
  3. 是“装饰”与调用堆栈的信息分配的内存部分,覆盖-区(又名红色区域),...

如果这4个部分清楚地分开,它也变得容易被另一个更换一个部件,或添加新的部分,它如:

  • 加入英特尔轮距构建模块库(的内存管理器实施第3部分)
  • 修改第1部分以支持新版本的编译器,一个新的平台或一个全新的编译器

自己写了一个内存管理器,我只能指出它可以很方便地有一个简单的方法来扩展自己的内存管理器。 例如我经常要做的是在长时间运行的服务器应用程序中查找内存泄漏。用我自己的内存经理,我不喜欢这样写道:

  • 启动应用程序,让它“热身”了一段时间
  • 问自己的内存管理器来转储使用的内存的概述,包括呼叫在呼叫
  • 继续运行的应用程序的时刻栈
  • 进行第二次转储
  • 两个字母顺序堆放在调用栈
  • 排序查找差异

虽然你可以用出的现成的组件类似的事情,他们往往有一些缺点:

  • 他们往往会严重减缓的应用
  • 他们往往只能报告泄漏在应用程序结束时,不在应用程序运行时

但是,也要尽量现实:如果您没有内存碎片,性能,内存泄漏或内存覆盖问题,那么就没有真正的理由写自己的米埃默里经理。

0

有一个像doxygen这样的开源软件使用的解决方案,当超过特定数量的内存时,想法是将一些实例存储到文件中。在你需要它们的时候从你的数据中提取数据。