![]() |
1
22
这变成了另一场“我的直觉”的火焰大战。一些变更的硬数据(文件包含细节、基准、图表等): http://www.cs.umass.edu/~emery/pubs/04-17.pdf 说: “结论。垃圾收集对性能影响的争论早已掩盖了它所提供的软件工程Benefi。本文介绍了一种基于跟踪和模拟的Oracle内存管理器。使用这个框架,我们使用垃圾收集和显式内存管理来执行一系列未更改的Java基准。比较运行时、空间消耗和虚拟内存占用情况,发现当空间充足时,垃圾收集的运行时性能可以与显式内存管理相竞争,甚至可以超过它4%。我们认为复制垃圾收集可能需要六倍于LEA或Kingsley分配器的物理内存,以提供类似的性能。”
当您有足够的内存时,复制GC将比显式的更快。
它还取决于你使用的语言——Java必须在每个集合上进行大量的重写(堆栈、对象、代),并且编写一个不必在JVM中停止世界的多线程GC将是一个伟大的成就。另一方面,在haskell中,几乎可以免费获得,其中GC时间很少为5%,而alloc时间几乎为0。这真的取决于你在做什么,在什么环境中。 |
![]() |
2
18
在垃圾收集的内存模型中,内存分配的成本通常要低得多,然后当只显式地使用new或malloc时,因为垃圾收集器通常预先分配这个内存。但是,显式内存模型也可以这样做(使用内存池或内存区域);使得内存分配的成本相当于指针添加。 AS Raymond Chen 和 Rico Mariani 指出,在一般情况下,托管语言倾向于执行非托管语言。但是,在推送之后,非托管语言可以并且最终将击败GC/JITTED语言。 同样的事情也在 Computer Language Shootout 因为尽管C++大部分时间都倾向于比Java高,但您经常会看到C++实现,通过各种环(例如对象池)跳跃来实现最佳性能。但是,垃圾收集的语言往往更易于遵循和更直接的实现,因为GC更擅长分配(小块)内存。 然而,性能并不是GC与非GC之间最大的区别;可以说,它是非GC(和引用计数)语言的确定性终结(或RIIA),是显式内存管理的最大论据,因为这通常用于内存管理以外的目的(如释放锁、关闭FI)。Le或窗把手等)。不过,最近C引入了using/idisposable构造来实现这一点。 垃圾收集的另一个问题是,它们使用的系统在防止内存泄漏方面往往相当复杂。但是,这也使得调试和跟踪内存泄漏变得更加困难(是的,即使垃圾收集的语言也可能有内存泄漏)。 另一方面,垃圾收集语言可以在最理想的时间(或近似的时间)做最理想的事情,而不必让开发人员负担该任务。这意味着为GC语言开发可能更自然,因此您可以将更多精力集中在 真实的 问题。 |
![]() |
3
6
下面是一个我喜欢运行的实验:
客观性提高:让你的祖母做第三步。 引用最佳GC实现的理论性能是非常好的,但事实上,在现实场景中,用垃圾收集语言编写的程序的性能不如本地应用程序好。这就是为什么性能直接转化为用户体验的大型项目仍在C++中编程的原因。典型的例子是游戏编程。 另一个可能违反直觉的例子是EclipseIDE。虽然它可以用Java编写 整个图形子系统必须重写 产生可接受的性能。解决方案:使GUI元素在本地(C/C++)组件上轻量级包装器 SWT ) 我了解垃圾收集环境的含义。内存管理很难纠正。还有很多工作。不过,底线是:知道程序应该如何运行,可以使您(程序员)在 machine trying to guess . |
![]() |
4
5
伯杰的论文被引用了很多,但它将真正的垃圾收集器与纯理论的离线优化算法进行了比较。所以尽管它可能会告诉你一些理论上的极限,
它很少提到
真实的
垃圾收集器与
真实的
的实现
这项研究并不完美,Zorn很小心地注意到,如果程序知道它们正在使用垃圾收集器,那么有些程序可以更快地完成。但硬数据是:
-在真正的程序中,最初编写用于
我从这个仔细的实验研究中学到的东西比伯杰对一个不可改进的理想化记忆管理器的研究要多。 |
![]() |
5
3
这里有很多不同的论点。我想先澄清一下,你不能真正地进行1:1的比较。每一个都有其优缺点,任何代码片段都将更适合于一个或另一个系统。相反,你必须知道你是否有GC来编写有效的代码。 我的论点是 你必须了解你的环境和代码。 . 这将使您的代码高效。从一个范例转移到另一个范例,并使用相同的样式进行编码,会使您的代码比GC真正帮助/带走的代码效率更低。 案例: 一个程序为短期对象分配数千个堆内存。也就是说,它使用不同大小的对象多次分配和解除分配。 在非GC环境中,对于每个分配,您最终都会调用malloc,这需要在空闲内存片段列表中找到最合适的一个(根据特定的malloc实现)。使用内存,然后释放自由[或新的/删除C++ +…]。内存管理的成本是定位片段的成本。 在GC环境中,使用可移动的GC作为Java或.NET,在每个GC运行之后,所有的空闲内存都是连续的。获取对象内存的成本很便宜,非常便宜(在Java VM中有10个CPU指令)。在每次GC运行时,只有活动对象被定位并移动到适当的内存区域的开头(通常它是与原始内存区域不同的区域)。内存管理的成本主要是移动所有可到达(活动)对象的成本。现在,前提是大多数对象都是短命的,所以最终的成本可能比非GC系统的成本要小。在一次GC运行中分配和释放(忘记)一百万个对象不需要额外的成本。 结论:在GC语言中,可以在堆上创建所有本地对象。它们很便宜。另一方面,在非GC系统中,大量的分配、解除分配和新的分配是昂贵的。内存碎片化,malloc的成本增加…在非GC系统中,您应该尽可能多地使用堆栈,而不必使用堆。 这还有另一个含义。习惯于两个内存系统之一的人往往会在另一个内存系统中编写效率低下的程序。它们用于一些在另一个系统中可能很糟糕的习语。 一个明显的例子是一个非托管程序员,它被用来分配一个对象,重用(根据需要用新元素重置其内部指针)被用来这样的思考方式:分配是昂贵的,重用是便宜的。现在,如果把相同的代码移到一个通用的GC环境(Java,.NET-两者都是移动代GC),你会得到一个有趣的效果。在Java代GC中,系统只对年轻一代执行小集合,只对完整集合中的旧代进行处理。但是,年轻一代的对象可以由老一代的对象引用,因此必须执行额外的工作来跟踪这些老的对象与年轻的引用。尤其是在 Java 1.4.1 garbage collector 系统将标记 存储卡 (页面的子部分)旧对象所在的位置,然后它包括在次要收集期间处理的所有标记卡,有效地增加了GC必须执行的工作量,并可能影响性能。 在1,2,3…GC运行,它被移动了很多次,最后被移动到旧一代,在旧一代中,它不会在每次GC运行中被移动,但可以站在那里…但遗憾的是,程序员强迫对象变年轻。它会再次移动,每次GC运行到它再次变旧时,它都会再次移动。 为了进行合理的比较,您需要让了解环境的程序员编写 不同的 用相同的算法和不同的内存管理思想来解决相同问题的代码片段。然后比较两种方法的结果。 |
![]() |
6
1
GC总是比极端的选择慢:完美的、非确定性的内存管理。 问题是:
还有一些领域是托管子系统战胜了非托管子系统: 一般来说,程序在多任务操作系统上的运行速度总是比在单任务操作系统(或没有操作系统的计算机)上慢。 一般来说,在有虚拟内存的系统上,程序的运行速度总是比没有虚拟内存的系统慢。 除了在极端情况下,我们是否认真考虑没有虚拟机和操作系统的计算机系统? |
![]() |
7
1
在讨论了另一个答案之后,发现GC的一个缺点是可以改变渐进复杂性。 Soru's comment 在没有示例的情况下隐式地陈述它,一条注释不足以解释它。感谢JonHarrop提供的示例以及对这个答案的有用评论。然而,正如我最后解释的,一个好的GC仍然应该分摊成本(考虑到足够的内存,一如既往)。
首先,当工作集的大小接近最大堆大小时,通常会发生这种情况。GC被调用得太频繁,因此一切都变慢了。为Eclipse安装scala插件,你会感觉到它的存在。 然而,通常情况下,如果您的算法只产生大量可快速检测到的垃圾,那么拥有足够的内存空间和一代GC就阻止了它的发生:它将无法存活足够长的时间来离开托儿所。 下面是一个例外的例子:让我们以“map f list”为例,假设f的每个应用程序都使用活动内存,方法是在哈希表中保存对返回值的引用。这里的渐进复杂性应该仍然是O(1)。 世代GC将无法通过收集托儿所释放内存,因此会定期调用一个主要集合(O(堆内容))。因此,我们得到运行时至少与(堆内容大小)*n*(每个调用f所消耗的空间)/(托儿所大小)成比例。 GC实际上会将托儿所的大小增加到指定的最大值,然后在n足够大的时候再次发生上述情况。但是,如果指定的最大值是大theta(最大堆大小),因此是omega(堆内容大小),则主集合将变得不常见,并且小集合的成本与生成的数据成比例(从而与生成它们所需的运行时成比例)。 这类似于需要复制容器以调整其大小时的情况:通过充分增长容器,可以分摊复制(或GC)的成本,并使其与生成副本所需的成本相当。 这个完整的答案并没有考虑到暂停时间的问题——收集变得很少,但是很长。它隐式地假定堆栈扫描时间是恒定的——但它确实应该是恒定的,除非您的算法是非尾部递归的,因此在这里考虑的大输入上存在问题。 最后,它不是关于增量垃圾收集器的。它们在根目录上解决了这个问题,但是它们主要用于实时GC,因为它们增加了内存读取的开销。 由于硬件支持优化了这一开销,Azul系统使用无暂停GC在自己的定制硬件上解决了这一问题。他们最近还声称已经解决了x86的问题,参见 this "press release" 和 this article .但是它确实在开发中,如果不更改操作系统,它就不会运行。完成后,如果表现如他们所说,也许这个问题也会为我们的普通凡人解决。 |
![]() |
8
0
理论上,一个好的程序可以通知一个智能GC子系统通过手动内存管理来实现所描述的加速。如果没有长时间运行,这些加速可能是不可见的,以摊销GC的固定启动成本。 在实践中,您可能无法通过当前的GC实现实现这些加速。此外,您将得不到一个明确的答案,因为这两种情况都会出现病态不良的情况。 |
![]() |
9
0
一个实用的问题是,使用显式mm,通常更容易分析、识别瓶颈并解决它。 有了GC系统,当您的好的O(N)代码以一种病态的方式垃圾GC,使其成为O(堆大小),就很难找出哪里出了问题。有时甚至像修复内存损坏一样困难。 |
![]() |
10
0
GC和任何malloc实现之间的根本区别在于GC跟踪
分配
对象,而malloc基本上跟踪
判定元件
分配的对象(通过“空闲列表”等,这些对象需要收集释放的内存块,以便在下面的malloc中快速返回它们)——一些malloc实现甚至没有可能(在其内部)通过设计枚举所有分配的块。
因此,任何可能的GC实现(不管它有多好),总是具有复杂性o(somefunc(n)),其中n是已分配对象的计数,而大多数malloc实现具有复杂性o(1)。因此,当(同时存在的)分配对象的数量越来越多时,任何GC的性能降级都是不可避免的(当然,性能可以用额外的内存消耗来交换)。[很明显,与分配的块/对象相比,空闲内存块的维护开销始终为零。]这是任何GC的基本缺陷,因为它
另外,malloc所指的不仅仅是特定的所谓的C函数,而是任何显式内存分配例程。 另外,我想指出的是,没有内置GC的编程语言提供了很多方法来绕过显式的MalC/C(Field/Delphi)调用(例如,STD::SydDypPTR在C++中或Obj-C中的ARC),这使得程序代码看起来与GC语言中的语言相似,但是从性能的角度来看,它与ExcCICI更接近(几乎等价)。t内存分配。 (我知道,即使是简单的引用计数也可以被视为垃圾收集的一种形式,但在本文中,gc的意思是更多的功能丰富的实现(至少具有引用周期的自动检测),这需要跟踪所有分配的对象,因此我不将std::shared ptr之类的包装器视为gc(至少是这个帖子) |
![]() |
11
0
不,他们测量了少量的面向对象的Java程序,它们运行在一个非生产性研究VM(Jikes)上,使用非常不寻常的GC算法,所有这些算法都已经过时了几十年。然后,他们根据给定的输入对每个程序进行分析,并计算出每个程序的最早点。
我没有任何问题:他们的结果有点有趣,但我并不惊讶(Java是一个非常糟糕的语言,这和他们所使用的GC算法相比,现代生产虚拟机,如热点或.NET)。然而,他们假装这些结果是概括的,这完全是荒谬的。没有理由相信他们的玩具GCS是生产GCS的代表,或OOP是所有程序的代表,或他们的“Oracle”内存管理是手动内存管理的代表。
对。不要那样做。
C++无法表达高效的GC算法(例如,没有办法去执行栈或更改调用约定),因此它永远不会有竞争性。如果你必须使用C++,忘记GC。如果你想要一个不错的GC,忘记C++。
这是不可能的,以一个有用的方式,因为存在或不存在一个GC是一个基本的设计决策,其他一切都是建立在这个基础之上的。 |
![]() |
12
-1
事实上,开发人员都是人,他们错过了一些事情,这首先导致了对垃圾收集器的需求。有人这么说,让我说垃圾收集总是比 很完美 显式内存管理。和垃圾收集 可以 通常比不完美的显式内存管理更快,因为垃圾收集器会清除开发人员容易忘记的内容。 |
![]() |
13
-2
理论上,气相色谱法 可以 在某些情况下要快一点,但我从来没有见过,我怀疑我会的。此外,C++与GC,如BEHM GC可能总是较慢,因为它是保守的。由于所有指针都在C++中进行篡改,所以GC必须假装。 一切 是指针。使用Java这样的语言,它可以准确地知道什么是指针,而不是指针,因此它可能具有更快的速度。 |
![]() |
14
-2
正如@dribeas所指出的,对(赫兹&berger)论文中的实验最大的“困惑”是,代码总是在一些“隐含的假设”下编写,即什么是便宜的,什么是昂贵的。除了这种混淆之外,实验方法(运行一个Java程序离线,创建一个对象生命周期的Oracle,在“理想的”OLC/FLASH调用中返回的工具)实际上是相当精彩和有启发性的。(我个人的观点是,混淆并不会对他们的结果造成太大的影响。) 就我个人而言,我的直觉是使用gc-ed运行时意味着接受对应用程序的三倍性能影响(gc将慢3倍)。但是,程序的实际情况充满了困惑,如果您能够在多个应用程序域的许多程序上执行“理想”实验,那么您很可能会发现一个巨大的分散的数据图,GC有时会赢,而手动通常会赢。(景观在不断变化——当多核(和为多核设计的软件)成为主流时,结果会发生变化吗?) 另见我的答案 Are there statistical studies that indicates that Python is "more productive"? 其中的一个论点是,“由于如此多的困惑,关于软件工程的所有证据都是奇闻轶事”。 |
![]() |
15
-2
旁注:另一个有趣的实验运行,我没有看到人们尝试,是比较只是泄漏。呼叫alloc,不要释放。这是一个有趣的选择。 除了长时间运行的服务器应用程序,实际上您永远不会耗尽内存,操作系统将开始使用磁盘作为虚拟内存(实际上,计算机具有无限的内存,达到虚拟地址空间的限制,我认为这对于64位计算机来说是巨大的)。这强调了GC只是一种改进局部性的设备。当你拥有无限的内存时,泄漏/死亡对象不会“伤害”,除了内存是分层的,你想让“活的”对象靠近并且快速,而“死的”对象在遥远/缓慢的内存中。如果每个对象都分配在不同的页面上,那么操作系统虚拟内存系统将是一个有效的GC。 |
![]() |
16
-4
也见 http://prog21.dadgum.com/40.html 讨论了“足够智能”的编译器。计算机/软件的前景充满了各种想法/技术, 可以 是 理论上 比现状更有成效。但都是蛇油。 GC现在很贵,而且可能永远都很贵。 |
![]() |
codeforester · 测量GC暂停时间的最佳方法是什么? 6 年前 |
![]() |
Venki WAR · 需要解释G1的并行完整GC 6 年前 |
![]() |
Stephan_Berlin · 为什么CMS系列中的初始标记阶段 6 年前 |
![]() |
Bonsaisteak · 为什么年轻一代需要三个区域来收集垃圾? 6 年前 |
![]() |
goks · 如何清除熊猫的数据帧内存? 6 年前 |