代码之家  ›  专栏  ›  技术社区  ›  MadH

是否有任何理由重载全局新建和删除?

  •  53
  • MadH  · 技术社区  · 15 年前

    除非你正在编程操作系统或嵌入式系统的一部分,有什么理由这样做吗?我可以想象,对于一些被创建和销毁的特定类,频繁重载内存管理函数或引入对象池可能会降低开销,但要全局执行这些操作吗?

    加法
    我刚刚在一个过载的删除函数中发现了一个错误——内存并不总是被释放的。这是在一个内存不足的应用程序中。此外,禁用这些重载只会将性能降低约0.5%。

    15 回复  |  直到 10 年前
        1
  •  76
  •   leander    15 年前

    我们重载全局的new和delete操作符,我在其中工作的原因有很多:

    • 池化 所有小的分配--减少开销,减少碎片,可以提高小的分配重的应用程序的性能
    • 框架 具有已知生存期的分配——忽略所有空闲空间,直到这段时间结束,然后一起释放所有空闲空间(诚然,我们在本地运算符重载中比全局运算符重载时做得更多)
    • 对准 调整——缓存线边界等
    • 同种填料 --帮助公开未初始化变量的用法
    • 自由填充 --帮助公开以前删除的内存的使用
    • 延迟自由 --提高自由填料的有效性,偶尔提高性能
    • 哨兵 蜡笔画 --帮助暴露缓冲区溢出、欠载和偶尔出现的野生指针
    • 重定向 分配——考虑到NUMA、特殊的内存区域,甚至保持内存中独立的系统(例如嵌入式脚本语言或DSL)
    • 垃圾收集 或者清除——对那些嵌入的脚本语言同样有用
    • 堆验证 --您可以每隔n个allocs/frees遍历堆数据结构,以确保一切正常。
    • 会计核算 包括 泄漏跟踪 使用率快照/统计信息 (堆栈、分配时间等)

    新的/删除记帐的思想是非常灵活和强大的:例如,您可以在每次发生alloc时记录活动线程的整个调用堆栈,并汇总有关它的统计信息。如果由于任何原因,您没有空间在本地保存堆栈信息,那么可以通过网络发送它。你可以在这里收集到的信息类型只受你的想象力(当然还有性能)的限制。

    我们使用全局重载是因为在那里挂起许多常见的调试功能很方便,并且根据我们从相同的重载中收集的统计数据,在整个应用程序中进行全面的改进。

    我们仍然为单个类型使用自定义分配器;在许多情况下,通过提供自定义分配器(例如,STL数据结构的单点使用)可以获得的加速或功能远远超过了全局重载可以获得的一般加速。

    看一下C/C++中的一些分配器和调试系统,你会很快想出这些和其他的想法:

    (一本古老但具有开创性的书是 Writing Solid Code ,它讨论了您可能希望在C中提供自定义分配器的许多原因,其中大部分仍然非常相关。)

    显然,如果你能使用这些好的工具中的任何一个,你将希望这样做,而不是滚动你自己的。

    在某些情况下,它更快、更容易、更少的业务/法律麻烦、您的平台还没有可用的东西,或者只是更具启发性:挖掘并编写一个全球性的过载。

        2
  •  26
  •   GManNickG    15 年前

    重载new和delete的最常见原因是检查 memory leaks 和内存使用统计。注意,“内存泄漏”通常被概括为内存错误。您可以检查诸如双重删除和缓冲区溢出之类的内容。

    之后的用途通常是内存分配方案,例如 garbage collection pooling .

    所有其他情况都只是一些特定的事情,在其他答案(登录到磁盘,内核使用)中提到。

        3
  •  15
  •   Crashworks    15 年前

    除了这里提到的其他重要用途(如内存标记)外,它也是强制应用程序中的所有分配进行固定块分配的唯一方法,这对性能和碎片化有着巨大的影响。

    例如,您可能有一系列具有固定块大小的内存池。覆盖全局 new 允许您将所有61字节的分配定向到(例如)具有64字节块的池,将所有768-1024字节的分配定向到1024B块池,将上述所有分配定向到2048字节块池,并将任何大于8KB的分配定向到一般不规则堆。

    由于固定块分配器比随意从堆中分配快得多,而且不太容易碎片化,这使得您可以强制甚至很糟糕的3D参与方代码从池中分配,而不是在整个地址空间中进行poop。

    这通常是在时间和空间至关重要的系统中完成的,例如游戏。280Z28、Meeh和Dan Olson描述了原因。

        4
  •  10
  •   Sam Harwell    15 年前

    Unrealengine3作为其核心内存管理系统的一部分,重载全局new和delete。有多个分配器提供不同的特性(分析、性能等),它们需要所有的分配来完成它。

    编辑:对于我自己的代码,我只会把它作为最后的手段。我的意思是,我几乎肯定不会使用它。但我的个人项目显然要小得多/要求非常不同。

        5
  •  6
  •   cwap    15 年前

    一些实时系统会过载,以避免在初始化后使用它们。

        6
  •  4
  •   Chris Masterton    15 年前

    重载新建和删除使您可以向内存分配中添加标记。我标记每个系统或控件或中间件的分配。我可以在运行时查看每种方法的使用量。也许我想看看从UI中分离出来的解析器的用法,或者中间件的实际使用量有多大!

    您还可以使用它在分配的内存周围放置保护带。如果/当你的应用程序崩溃时,你可以看看地址。如果您将内容视为“0xabcdao”(或任何您选择作为保护的内容),则表示您正在访问不属于自己的内存。

    也许在调用了delete之后,您可以用类似的可识别模式填充这个空间。 我相信VisualStudio在调试中做了类似的事情。它不是用0xCDCDCDCD填充未初始化的内存吗?

    最后,如果您有碎片问题,可以使用它重定向到块分配器?我不确定这到底是多久一次的问题。

        7
  •  3
  •   Edouard A.    15 年前

    当调用new和delete在您的环境中不起作用时,您需要重载它们。

    例如,在内核编程中,默认的new和delete不起作用,因为它们依赖用户模式库来分配内存。

        8
  •  2
  •   Juan    15 年前

    从实际的角度来看,最好在系统库级别覆盖malloc,因为operator new可能无论如何都会调用它。

    在Linux上,您可以将自己的malloc版本替换为System One,如下面的示例所示:

    http://developers.sun.com/solaris/articles/lib_interposers.html

    在那篇文章中,他们试图收集性能统计数据。但是如果您也覆盖了free,那么您也可能检测到内存泄漏。

    因为您是在一个带有ld_预加载的共享库中执行此操作,所以您甚至不需要重新编译应用程序。

        9
  •  2
  •   macbutch    15 年前

    我在一个“安全”系统中看到过 * 需要原因来重写它在取消分配时使用的所有内存。方法是在每个内存块的开头分配额外的几个字节,这些字节将包含整个块的大小,然后在删除时用零覆盖整个块。

    正如您可能想象的那样,这有许多问题,但它确实起到了作用(大部分),并且避免了团队在一个相当大的现有应用程序中审查每个内存分配。

    当然不是说这是一个很好的用途,但它可能是其中一个更有想象力的…

    * 可悲的是,与其说是真正的安全,不如说是安全的外表……

        10
  •  2
  •   Ben Lings    15 年前

    用C++编写的PS图象处理软件插件应该重写 operator new 这样他们就可以通过photoshop获得内存。

        11
  •  2
  •   Martin Beckett    15 年前

    我用内存映射文件来完成这项工作,这样写入内存的数据也会自动保存到磁盘上。
    如果您有内存映射的IO设备,或者有时需要分配某个连续内存块,它还用于返回特定物理地址的内存。

    但99%的时间是作为调试功能完成的,用来记录分配和释放内存的频率、位置和时间。

        12
  •  2
  •   Dan Olson    15 年前

    实际上,对于游戏来说,从系统中分配一大块内存,然后通过重载new和delete提供定制的分配器是很常见的。一个很大的原因是控制台的内存大小是固定的,这使得泄漏和碎片化都有很大的问题。

    通常(至少在封闭平台上)默认堆操作缺少控制和自省。对于许多应用程序来说,这并不重要,但是对于在固定内存情况下稳定运行的游戏来说,增加控制和内省都是非常重要的。

        13
  •  2
  •   Mohammed Arif    15 年前

    对于您的应用程序来说,除了随机崩溃之外,它还能够响应内存不足的情况,这是一个很好的技巧。做这个你的 new 可以是默认的简单代理 新的 它捕捉失败,释放一些东西,然后再试一次。

    最简单的技术是在启动时为这个目的保留一个空白内存块。你也可能有一些缓存你可以利用-想法是一样的。

    当第一次分配失败开始时,您仍然有时间警告用户内存不足的情况(“我可以再活一段时间,但您可能希望保存您的工作并关闭一些其他应用程序”),将状态保存到磁盘,切换到生存模式,或者在您的上下文中任何其他有意义的情况。

        14
  •  0
  •   Tobias    15 年前

    最常见的用例可能是泄漏检查。

    另一个用例是,当您的环境中有特定的内存分配需求,而您使用的标准库不满足这些需求时,例如,您需要确保在多线程环境中内存分配是无锁的。

        15
  •  0
  •   Runeborg    15 年前

    正如许多人所说,这通常在性能关键型应用程序中完成,或者能够控制内存对齐或跟踪内存。游戏经常使用自定义内存管理器,尤其是针对特定平台/控制台时。

    这是一个相当不错的 blog post about one way of doing this 还有一些推理。