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

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

我认为你最好的选择是不要实施一个,直到配置文件证明 CRT以损害应用程序性能的方式分割内存。 CRT,核心操作系统和STL的人花了很多时间考虑内存管理。

很有可能你的代码在现有的分配器下性能很好,不需要修改。 这当然有一个更好的机会,比你第一次得到一个内存分配器。 在类似的情况下,我已经写了内存分配器,这是一个艰巨的任务。 不那么令人惊讶的是,我继承的版本充满了碎片问题。

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

只要你使用标准的集合类,一个算法(比如STL / BOOST),在循环的后面插入一个新的分配器不是很困难的,以便修复你需要的代码库部分固定。 您的整个程序都不需要手动编码分配器。

尽管大多数人表示不应该编写自己的内存管理器,但在以下情况下仍然有用:

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

如果你想编写你自己的内存管理器,重要的是把它分成以下4个部分:

  1. 一个“拦截”malloc / free(C)和new / delete(C ++)调用的部分。 这是很容易的新/删除(只是全球新的和删除运营商),但也为malloc /免费这是可能的('覆盖'的功能,重新定义调用malloc /免费…)
  2. 一个表示内存管理器入口点的部分,由“拦截器”部分调用
  3. 实现内存管理器的一部分。 可能你会有多种实现(取决于情况)
  4. 一个部分“装饰”分配的内存与调用堆栈的信息,覆盖区域(又名红色区域),…

如果这四个部分清楚地分开,则可以很容易地将一个部分替换为另一个部分,或者添加一个新的部分,例如:

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

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

  • 启动应用程序,让它“热身”一段时间
  • 请求您自己的内存管理器转储所使用内存的概览,包括调用时的调用堆栈
  • 继续运行应用程序
  • 做第二个转储
  • 在调用堆栈上按字母顺序排序两个转储
  • 查看差异

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

  • 往往会严重拖慢应用程序
  • 通常他们只能在应用程序结束时报告泄漏,而不是在应用程序运行的时候

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

是来自MicroQuill的SmartHeap吗?

曾经有一个优秀的第三方嵌入式堆栈替换库,但是我不记得这个名字了。 我们的应用程序开始使用时速度提高了30%。

编辑:这是SmartHeap – 谢谢,ChrisW

根据我的经验, 当你连续分配和释放大缓冲区 (比如超过16k) 碎片化就成了一个问题,因为如果堆中的一个不能找到足够大的空间,那么最终会导致内存不足。 。

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

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

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

标准库通常足够好。 如果不是这样,而不是取而代之,更小的一步是重写operator newoperator delete ,而不是所有的类。

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

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

这些是一些一般的事情要考虑:

首先,快速分配和销毁的小物件应放在堆叠上。 最快的分配是从来没有完成的。 堆栈分配也没有锁定全局堆,这对于多线程代码是有利的。 在c / c ++中分配堆可能比java这样的GC语言相对昂贵,所以尽量避免这种情况,除非你需要它。

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

我不会。

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

我会寻找一个专门的图书馆,而不是重新发明轮子。

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