【问题标题】:When can garbage collection be faster than manual memory management? [closed]垃圾收集何时能比手动内存管理更快? [关闭]
【发布时间】:2011-10-22 01:28:09
【问题描述】:

什么情况下垃圾回收比手动内存管理更高效? (这里的手册可能意味着使用 C 中的 malloc 和 free,或者 C++ 普及的更清洁的 RAII 和智能指针技术)

我喜欢垃圾收集如何消除编写软件时的一些意外复杂性,但我更高兴的是 RAII 和智能指针如何消除这种复杂性,同时还能处理内存以外的资源,具有确定性,并提供性能保证和整体效率更高。所以我想我可以放心地忽略垃圾收集。但是,我注意到人们一直在说垃圾收集比 C++ 中使用的严格资源管理更快,例如在有大量额外内存可用时。

那么垃圾回收究竟什么时候可以胜过手动内存管理呢?我喜欢 RAII 和智能指针,但如果它更快,我很乐意接受垃圾收集作为另一种工具。

【问题讨论】:

  • 垃圾收集速度更快的时候是什么都不需要释放。如果您的程序不使用大量内存资源,垃圾收集器可能永远不需要启动。GC 系统中的分配器可能就像移动指针一样简单。
  • GC 性能高度依赖于所选择的算法。 “手动”内存管理器也是如此。我们最多只能将苹果与橙子进行比较。
  • @Michael Burr:在这种情况下,手动管理可能同样便宜。您实质上是在描述批量解除分配的情况,并且程序在整批分配被解除分配之前终止。这对于 GC 和 malloc/free 实现都是可能的。
  • 老实说,我认为您问错了问题。更好的问题是,“GC 是否达到或超过了我的应用程序的性能预算以及 GC 带来了哪些优势?”
  • @MSalters:如果所有分配/解除分配代码都在您的控制之下,这是正确的,但是如果您使用库代码(无论是您自己的还是其他人的)或使用 RAII,您通常会失去对一定程度上的释放政策。无论如何,我将其放在评论中,因为我认为这并不是一个重要的性能考虑因素。它可能会在较小的程序/实用程序中以某种理论上的方式发挥作用,但我认为在这些情况下它不会成为任何真正的“胜利”。

标签: c++ garbage-collection raii


【解决方案1】:

从来没有,我可以证明这一点。

首先,假设我们在任何一种情况下都使用了最好的算法。使用次优算法可以证明任何事情。

其次,让我们假设最好的 GC 算法使用时间 T0...Tn 来决定是否应该在某个时刻释放内存分配 i。那么总数是Σ(Ti)

现在,存在一个等效的手动内存管理算法,它使用相同的 GC 算法,但只考虑手动标记为释放的内存块。因此,运行时间为Σ(δiTi)(当块 i 未被释放时 δi=0,当它被释放时 =1)。

显然,Σ(δiTi) ≤ Σ(Ti):有一个手动算法严格来说并不比 GC 算法差。

这与其他答案有何不同?

  • “使用 RAII,每次分配时都必须解除分配。” - 不,这是一个错误的假设。我们应该比较批处理或非批处理操作。如果每次超出范围时还运行 GC,GC 会更糟糕。
  • “改进的局部性” - 好吧,除非您忽略非 GC 语言可以更频繁地使用堆栈的事实,并且该堆栈具有出色的引用局部性。
  • “泄漏几率低” - 这完全正确,但我们正在讨论运行时性能。 (顺便说一句:很高兴指出它的几率很低但非零)。

【讨论】:

  • 你只是用你的数学来解决释放(免费)问题。这足以证明 GC 总体上是劣等的?
  • @Zach Saw:对于等价证明,您必须在两种情况下使用相同的分配机制。所以分配也得到了解决(可能不够明确)。
  • @MSalters:“仅考虑手动标记为释放的内存块”-您首先没有考虑执行此标记所需的时间,没有时间完全用在 GC 语言中。是不是需要解决这个问题才能使这个证明起作用?
  • @MSalters:“假设我们在任何一种情况下都使用最好的算法”(什么是“最好的”?),你不能“在两种情况下都使用相同的分配机制”——只是没有意义。 GC 分配时间是 Mark-Sweep-Compact 的指针碰撞。手动内存管理需要更复杂的分配策略。
  • @MSalters:你在这里提出的数学完全有缺陷,因为一开始的假设是不正确的。
【解决方案2】:

我知道有一种特殊情况,其中 GC 指针比智能指针(引用计数指针)快得多,因为两者都是用传统 C++(即不是 C++/CLR,因为我们不会像 Compact Mark-Sweep 之后的内存,我们在这里尽可能多地比较苹果和苹果)假设 GC 使用相同的底层堆内存管理器。这是您的对象分配时间大大超过对象创建时间的时候。

示例包括排序、数组反转等

请参阅此处了解more info on my test with a GC framework I implemented using traditional C++ 与引用计数指针的对比。数组反转测试的结果是 GcString 比 ref counted String 快 16.5 倍。

这可能归因于引用计数指针中非常缓慢的总线锁语义(除非您的目标是纯粹的单线程应用程序,否则线程安全需要锁)。根据我在传统 C++ 中实现高性能精确 GC 的经验,我可以说使用 GC 与 'manual'(根据 OP 对手动的定义)内存管理(即智能指针)。

【讨论】:

  • 你应该对引用计数使用原子操作,而不是锁。
  • @GMan:除非您在助记符前面加上 lock 前缀,否则原子操作不会锁定。还是您建议使用隐式锁定操作码,例如 xchg?无论哪种方式,它们都是锁。
  • 'Lock' 通常被定义为 OS 锁,而原子操作可能被认为是“CPU 锁”。无论如何,类别不是重点,而是实施。操作系统锁是多余的(如果有必要,唯一合适的就是自旋锁)。
  • @GMan:哦,我现在明白你想提出什么建议了。您是否认为引用计数指针正在使用关键部分?大声笑...
  • 就像我说的,“锁”通常被定义为“操作系统锁”,而您会使用“原子操作”阶段来表示“CPU“锁”。
【解决方案3】:

GC优势:

  • GC 仅通过递增指针进行分配,堆分配器必须采取对策以避免堆碎片
  • GC 提高了 cpu 缓存局部性,这对现代处理器来说意义重大
  • GC 不需要额外的代码来释放内存,泄漏的可能性很小
  • 世代 GG 可以同时收集垃圾,使多核 cpu 上的内存管理接近空闲。

GC 缺点:

  • 很难在将指针视为第一类类型的语言中提高效率
  • 由于收集延迟而使用更多虚拟内存空间
  • 内存以外的操作系统资源的抽象泄漏
  • 在某些情况下可能会在垃圾收集过程中导致程序运行暂停。

它是性能的一个大满贯,GC 轻松击败堆分配器。 Rico Mariani 和 Raymond Chen 之间的中文词典编程比赛经常被引用,概述is here。 Raymond 的 C++ 程序最终获胜,但只是在多次重写并放弃标准 C++ 库之后。

【讨论】:

  • 除非使用 Mark-Sweep-Compact 算法(即 MS .NET GC),否则 GC 不能简单地碰撞指针来分配内存。在非 C++/CLR(即传统的 C++)中,您将无法执行 Compact 部分。
  • 另外,请注意,分代 GC 是并发 GC 的正交概念。
  • 当页面不再使用时,GC 仍然需要向操作系统释放内存。同样,堆内存管理器也可以保留这个未使用的页面以供将来使用。
  • 您的结论充其量是误导性的。 Mariani/Chen 的比较与内存管理几乎没有任何关系。所做的很少是因为当时的 C++ 没有 move 构造函数(它现在有)。最后,值得怀疑的是,任何知道自己在做什么的人是否会编写类似于 Raymond Chen 的初始版本(或大多数中间步骤)的代码,除非作为一个稻草人,以备后来打倒。最后,有问题的代码是一个非常奇怪的任务,它是否真的对任何人都有意义是值得怀疑的。
  • 好吧,这个答案和我预期的一样被讨厌。您可以随意将陈视为“糟糕的程序员”,但这完全没有抓住重点。他最终确实制作了一个击败 Mariani 版本的版本。关键是他花了多个版本和几个星期才到达那里。托管代码可以提高生产力,仅此而已。至于 C++ 在陷入泥潭多年后获得移动语义,也许你应该考虑为此感谢强大的竞争:)
猜你喜欢
  • 2013-04-25
  • 2011-02-26
  • 1970-01-01
  • 2012-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-30
  • 2011-02-28
相关资源
最近更新 更多