我想知道垃圾回收的最佳替代方案及其优缺点。我的优先考虑因素是速度,内存不太重要。如果有不会造成任何暂停的垃圾回收器,请告诉我。
我正在开发一种安全语言(即没有悬空指针、检查边界等),必须使用垃圾回收或其替代方案。
我想知道垃圾回收的最佳替代方案及其优缺点。我的优先考虑因素是速度,内存不太重要。如果有不会造成任何暂停的垃圾回收器,请告诉我。
我正在开发一种安全语言(即没有悬空指针、检查边界等),必须使用垃圾回收或其替代方案。
我认为最好还是使用垃圾回收(像JVM一样),除非你有非常充分的理由。现代的垃圾回收器非常快速、通用且安全。除非你的语言可以利用非常特定的特殊情况(例如上述分配器之一)来设计,否则你不太可能打败JVM。
目前唯一真正引人注目的反对现代垃圾回收的理由是由GC暂停引起的延迟问题。这些问题很小、很少见,在大多数情况下都不是问题(例如我已经成功地在Java中编写了3D引擎),但在非常紧密的实时情况下仍然可能会导致问题。
尽管如此,仍可能有一些特殊情况需要采用不同的内存分配方案,因此我列出了以下一些有趣的选项:
一个非常快速、专业化的内存管理方法的例子是许多游戏中使用的“每帧”分配器。这通过递增单个指针来分配内存,在一个时间段结束时(通常是一个可视化的“帧”),所有对象一次性被丢弃,只需将指针设置回基地址并在下一个分配中覆盖它们即可。这可能是“安全”的,但对象生命周期的约束非常严格。如果你可以保证所有内存分配的大小都是有界的,并且仅在处理例如单个服务器请求的范围内有效,那么这可能是一个赢家。
另一个非常快速的方法是为不同类别的对象建立专用的对象池。释放的对象可以在池中被回收利用,使用类似于空闲对象插槽的链接列表。操作系统经常使用这种方法来处理常见的数据结构。然而你需要注意对象的生命周期并通过将对象返回到池中显式地处理处理器的拆卸。
引用计数看起来表面上很好,但通常并不实用,因为当你更改指针值时,经常需要对两个对象进行解引用和更新计数。这一成本通常比简单、快速的内存管理的优点更加糟糕,而且它也不能在存在循环引用的情况下使用。
堆栈分配非常快速并且可以安全运行。根据您所使用的语言,可能可以在不使用堆的情况下完全依赖于基于堆栈的系统。然而,我怀疑这将在一定程度上限制您的语言设计,因此可能是无法启动的。对于某些DSLs可能仍值得考虑。
经典的动态内存分配(malloc/free)相当快速,并且如果您在对象创建和生命周期上有足够的约束,则可以使其安全,这些约束可以在您的语言中强制执行。例如,如果您对指针的使用施加了重大限制。
总之 - 希望这为您提供了有用的思路!
使用C++,可以为对象进行一次堆分配,然后重复使用该内存以供后续对象使用。我见过它的工作效果,非常快速。
这仅适用于某些特定问题,并且很难正确实现,但确实是可能的。
C++的乐趣之一在于您完全控制内存管理,可以决定使用经典的new/delete,或者实现自己的引用计数或垃圾回收。
然而,这里有风险——你真的需要知道你在做什么。
如果内存不重要,那么@Thomas所说的就适用了。考虑到现代硬件的巨大内存空间,这可能是一个可行的选择——它实际上取决于进程。
手动内存管理不一定会直接解决您的问题,但它确实可以让您完全控制内存事件发生的时间。例如,通用的malloc操作并不是O(1)操作。它在其中做了所有可能的可怕事情,既在由malloc本身管理的堆内,也在操作系统内部。例如,你永远不知道“malloc(10)”何时会导致VM将某些内容分页出去,现在你的10字节RAM有一个未知的磁盘I/O组件——哎呀!更糟糕的是,这个页面可能是你的内存,你需要立即将它分页回来!现在c=*p是一个磁盘命中。耶!
但是,如果您意识到了这些问题,那么您就可以安全地设置自己的代码,以便所有时间关键的部分实际上不进行内存管理,而是使用预先分配的结构来完成任务。
使用GC系统,您可能会有类似的选项——这取决于收集器。例如,我认为Sun JVM没有短时间内“关闭”的能力。但是,如果您使用预分配的结构,并调用自己的所有代码(或者知道您调用的库例程中发生了什么),那么您很可能不会遇到内存管理问题。
因为,问题的关键在于内存管理是一项艰巨的工作。如果您想摆脱内存管理,可以使用旧式的FORTRAN和ARRAYs以及COMMON块进行编写(这也是FORTRAN之所以如此快的原因之一)。当然,您可以在大多数任何语言中编写“FORTRAN”。
对于现代语言、现代GC等,内存管理已经被推到了一边,成为了一个“10%”的问题。我们现在非常随意地创建垃圾、复制内存等等,因为GC等使我们变得更加随意。对于90%的程序来说,这不是问题,所以我们不必担心。现在,这是一个后期调整的问题。
因此,你最好一次性将所有东西都设置好,使用它,然后将所有东西丢弃。 "使用它" 部分是你将获得一致、可靠的结果的地方(当然,假设系统有足够的内存)。
作为垃圾回收的“替代方案”,C++ 特别提供了智能指针。boost::shared_ptr<>(或 std::tr1::shared_ptr<>)与 Python 的引用计数垃圾回收完全相同。在我看来,shared_ptr 就是垃圾回收。(尽管您可能需要执行一些 weak_ptr<> 操作以确保不会发生循环引用)
我认为 auto_ptr<>(或在 C++0x 中,unique_ptr<>...)是一个可行的替代方案,具有自己的一套优点和权衡。Auto_ptr 语法笨拙,不能用于 STL 容器...但它可以完成工作。在编译时,您将指针的所有权从变量移动到变量。如果一个变量在其作用域结束时拥有指针,则会调用其析构函数并释放内存。只允许一个 auto_ptr<>(或 unique_ptr<>)拥有真正的指针。(至少,如果您使用正确的话)。
作为另一种选择,您可以将所有内容存储在堆栈上,并仅传递引用到您需要的所有函数中。
这些替代方案并不能真正解决垃圾回收所解决的一般内存管理问题。尽管如此,它们是高效和经过充分测试的。auto_ptr不会使用比指针最初使用的空间更多的空间...并且在dereferencing auto_ptr时没有开销。 "移动"(或在Auto_ptr中赋值)具有少量开销以跟踪所有者。我没有做过任何基准测试,但我相当确定它们比垃圾回收/ shared_ptr更快。
shared_ptr
会产生任意长的暂停。 - J Dauto_ptr
获得类似的效果。 - J D如果您真的想完全没有暂停,除了堆栈分配、基于区域的缓冲区和静态分配之外,禁止所有内存分配。尽管您可能听说过的是malloc()
不会导致严重的暂停,但如果空闲列表变得碎片化,它实际上会导致严重的暂停;如果您经常发现自己构建大型对象图,则天真的手动释放将输给停止和复制;真正避免这种情况的唯一方法是分摊预分配页面,例如堆栈或一次性释放的撞球分配池。我不知道这有多有用,但我知道专有的图形编程语言LabVIEW默认为每个子程序等效项分配一个静态内存区域,需要程序员手动启用堆栈分配;这是在需要对内存使用量进行绝对保证的硬实时环境中非常有用的。
有一个普遍的谬论,认为托管语言不适合高性能低延迟场景。是的,在资源有限(如嵌入式平台)和编程不规范的情况下,你可能会像使用C++一样惨痛地自毁(而且这可能非常非常壮观)。
在Java/C#开发游戏时遇到了这个问题,解决方案是利用内存池并且不让对象死亡,因此在你不希望它运行时不需要垃圾回收器。这实际上与低延迟的非托管系统采用的方法相同 - 努力尽量不分配内存。
因此,考虑到在Java/C#中实现这样的系统与C++非常相似,以“女人般软弱”的方式(托管方式)进行操作的优势在于,你可以享受其他语言特性的“美好”,从而释放出更多的精力集中于重要事情上。