任何理由超载全球新和删除?
除非你编程操作系统或embedded式系统的一部分,否则有什么理由这样做? 我可以想象,对于经常被创build和销毁的某些特定的类来说,重载内存pipe理函数或者引入一个对象池可能会降低开销,但是在全局上做这些事情呢?
加成
我刚刚发现一个重载删除函数的错误 – 内存并不总是被释放。 这是一个不太重要的内存关键应用程序。 而且,禁用这些过载只会使性能降低0.5%左右。
在我工作的地方,我们重载全局新的和删除操作符的原因很多:
- 汇集所有小的分配 – 减less开销,减less碎片,可以提高小型应用程序的性能
- 用已知的生命周期来分配分配 – 直到这个时期结束时,忽略所有的释放,然后把所有的释放放在一起(当然,我们用本地操作符重载来做比全局更多的事情)
- alignment调整 – caching线边界等
- alloc fill – 帮助公开使用未初始化的variables
- 自由填充 – 帮助公开以前删除的内存的使用
- 免费延迟 – 增加免费补充的效力,偶尔会提高性能
- 哨兵或fenceposts – 帮助揭露缓冲区超支,underruns和偶尔的野生指针
- redirect分配 – 考虑到NUMA,特殊内存区域,甚至将单独的系统分开存储(例如embedded式脚本语言或DSL)
- 垃圾收集或清理 – 对于那些embedded式脚本语言来说也是有用的
- 堆validation – 你可以遍历堆数据结构每N分配/释放,以确保一切看起来不错
- 会计 ,包括泄漏跟踪和使用快照/统计 (堆栈,分配年龄等)
新/删除会计的思想是非常灵活和强大的:例如,您可以在发生分配时logging活动线程的整个调用堆栈,然后汇总相关统计信息。 如果因为某种原因没有足够的空间保存在本地,则可以通过networking发送堆栈信息。 您可以在这里收集的信息types仅受限于您的想象力(当然还有性能)。
我们使用全局重载,因为在那里挂载大量常用的debuggingfunction非常方便,并且根据我们从这些相同过载收集的统计数据,在整个应用程序中进行全面的改进。
我们仍然使用自定义分配器的个人types也; 在许多情况下,通过为STL数据结构的单个使用点提供自定义分配器,您可以获得的加速或性能远远超过您可以从全局超载获得的通用加速。
看一下C / C ++的一些分配器和debugging系统,你会很快想出这些和其他的想法:
- 的valgrind
- 电围栏
- dmalloc
- dlmalloc
- 应用程序validation器
- 保证++
- 的BoundsChecker
- …和许多其他…(gamedev行业是一个很好的地方看看)
(一本古老而有创意的书是“ 编写固体代码” ,它讨论了许多您可能想要在C中提供自定义分配器的原因,其中大部分依然非常相关。)
显然,如果你可以使用这些好工具,你会想这样做,而不是自己动手。
有些情况下,它更快,更容易,更less的商业/法律麻烦,没有什么可用于您的平台,或只是更有启发性:挖掘和写入全球超载。
重载new和delete最常见的原因就是检查内存泄漏和内存使用情况。 请注意,“内存泄漏”通常泛化为内存错误。 您可以检查双重删除和缓冲区溢出等事情。
之后的用途通常是内存分配scheme,如垃圾收集和池化 。
所有其他情况只是具体的事情,在其他答案中提到(logging到磁盘,内核使用)。
除了这里提到的其他重要用途,如内存标记,它也是强制应用程序中的所有分配通过固定块分配的唯一方法,这对性能和碎片有巨大的影响。
例如,您可能有一系列固定块大小的内存池。 覆盖全局new
可以让你将所有61字节的分配指向64字节块的池,所有的768-1024字节分配给1024b块的池,所有那些分配到2048字节块池,大于8kb到一般不整齐的堆。
因为固定块分配器比分配堆栈更加快速,不容易碎片化,这使得您可以强制甚至蹩脚的三方代码从池中分配,而不是遍布整个地址空间。
这经常在时间和空间关键的系统中完成,例如游戏。 280Z28,Meeh和Dan Olson描述了为什么。
UnrealEngine3将全局新build和删除作为其核心内存pipe理系统的一部分进行重载。 有多个分配器提供不同的function(分析,性能等),他们需要所有的分配来通过它。
编辑:对于我自己的代码,我只会做最后的手段。 我的意思是,我几乎肯定不会使用它。 但是我的个人项目显然要小得多/非常不同的要求。
一些实时系统将它们重载,以避免它们在init之后被使用。
重载新的和删除使得添加标签到你的内存分配成为可能。 我为每个系统或控件或中间件分配标签。 我可以在运行时查看每个用户的使用情况。 也许我想看看从UI中分离出来的parsing器的用法,或者一个中间件实际上使用了多less!
您也可以使用它来在分配的内存周围放置防护带。 如果/当你的应用程序崩溃,你可以看看地址。 如果你看到内容为“0xABCDABCD”(或任何你select的守卫),你正在访问你不拥有的内存。
也许在调用delete之后,可以用类似的可识别模式填充这个空间。 我相信VisualStudio在debugging中做了类似的事情。 是不是用0xCDCDCDCD填充未初始化的内存?
最后,如果你有碎片问题,你可以使用它redirect到块分配器? 我不知道这多么经常是一个问题。
当您的环境中新的和删除的呼叫不起作用时,您需要重载它们。
例如,在内核编程中,默认的new和delete不起作用,因为它们依赖于用户模式库来分配内存。
从实际的angular度来看,在系统库层次上覆盖malloc可能会更好,因为operator new可能会调用它。
在linux上,你可以把你自己的malloc版本replace成系统版本,如下面的例子:
http://developers.sun.com/solaris/articles/lib_interposers.html
在那篇文章中,他们试图收集性能统计数据。 但是,如果您也覆盖免费,您也可能检测到内存泄漏。
既然你在LD_PRELOAD的共享库中这样做,你甚至不需要重新编译你的应用程序。
我已经看到它在一个为了“安全性”的系统中完成*
原因是需要写入所有用于解除分配的内存。 该方法是在每块内存的开始处分配额外的几个字节,其中包含整个块的大小,然后在删除时用零覆盖。
这有很多问题,你可能可以想象,但它确实(大部分)工作,并保存在一个相当大的,现有的应用程序中检查每一个内存分配的团队。
当然不是说这是一个很好的用途,但它可能是其中更有想象力的一个…
*
可悲的是,它并不像安全的外观那么关乎实际安全。
用C ++编写的Photoshop插件应该重写operator new
以便通过Photoshop获得内存。
我已经使用内存映射文件完成了这一工作,以便写入内存的数据也自动保存到磁盘。
如果您有内存映射的IO设备,或者有时需要分配一定的连续内存块,它也用于在特定物理地址处返回内存。
但99%的时间是作为debuggingfunction完成的,logging内存分配和释放的频率。
实际上,游戏从系统中分配一大块内存是非常常见的,然后通过重新载入和删除来提供自定义分配器。 一个重要的原因是控制台的内存大小固定,这使得泄漏和碎片化成为一个大问题。
通常(至less在一个封闭的平台上)缺省的堆操作是缺乏控制和缺乏内省的。 对于许多应用来说,这并不重要,但是对于游戏在固定内存情况下运行稳定而言,增加的控制和内省都是非常重要的。
对于您的应用程序来说,能够通过除了随机崩溃之外的其他内容来响应低内存条件可能是一个很好的窍门。 要做到这一点,你的new
可以是一个简单的代理到默认的new
捕获它的失败,释放一些东西,并再次尝试。
最简单的方法就是在启动时为这个目的预留一块空白的内存。 你也可以有一些caching,你可以挖掘 – 这个想法是一样的。
当第一次分配失败时,你仍然有时间警告你的用户有关内存不足的情况(“我可以再活一段时间,但你可能想保存你的工作,closures一些其他的应用程序”),保存你的状态到磁盘,切换到生存模式,或其他任何有意义的上下文。
最常见的用例可能是泄漏检查。
另一个用例是当你的环境中有特定的内存分配要求时,你所使用的标准库不满意,例如,你需要保证内存分配在multithreading环境下是无锁的。
正如许多人已经说过的,这通常是在性能关键的应用程序中完成的,或者是能够控制内存alignment或跟踪内存。 游戏经常使用自定义内存pipe理器,特别是在针对特定平台/控制台时。
这是一个相当不错的博客文章,关于这样做的一种方式和一些推理。