百科问答小站 logo
百科问答小站 font logo



既然引用计数在做 GC 时有性能问题,为什么智能指针会广泛应用它? 第1页

  

user avatar   s.invalid 网友的相关建议: 
      

简单说,单纯的引用计数做不了GC。这是因为引用计数存在循环引用的问题:比如A引用了B,B又引用了A,当其它对象全部删除了自己对A和B的引用时,A和B会因为彼此引用而永远不能被回收,这就会造成内存泄漏。


如果你想弥补这个缺陷,那么就不得不另外建立一张“网”,通过这个网找到A和B,检测它们是否存在循环引用——甚至于,是否存在A->B->C->D->E->F->A这样的大循环、甚至是否有错综复杂的依赖网问题。

那么,这套补足算法必然是非常低效的:第一,它必须定期执行(如何确定执行时机?);第二,它必须遍历所有现存对象;第三,它必须执行一套复杂的循环引用发现算法。


换句话说:引用计数不做GC、而是作为某种轻量级的GC代替品时,它是高效的、行为极度可控的;但一旦想基于它做GC,这东西就完蛋了


在C++里面,从没有人拿引用计数当GC用


事实上,当我们费劲巴拉的去“发现循环依赖”时,我们其实就实现了一个mark and sweep或者分代GC方案——于是乎,我们就相当于在一个常规GC之外、额外维护了一套引用计数体系,以实现“资源的即时释放”。


换句话说,并不是“引用计数性能比GC差”,而是“在GC之外额外维护一套引用计数体系就会额外付出很多性能”——再换句话说,为了“资源的即时释放”而在常规GC之外附加一套引用计数系统是得不偿失的。

这就是“引用计数当GC用性能差“的根本原因。

但如果不把引用计数当作GC来用、而是审慎有节制的用于受限场景时,引用计数就是既轻量又及时的完美品——完美到极大的压制了“给C++引入真正的GC”的需求。


当然,你的确可以构造一些特殊场景,让“引用计数”相对于GC算法显得低效。

比如,给一组对象反复addref/release,那么引用计数在每次add/release时都不得不更新ref_count(尤其多线程环境下,每次更新都还伴随着性能消耗严重的加锁操作);而GC呢,人家可以视若无睹,直到定时时间到,于是stop world并扫描一次——引用计数被指使着做了无数的无用功,而GC仅仅做了一次批量操作,那么性能表现当然会远高于前者。


但这种场景是极其少见的。尤其C++程序员早就习惯了通盘考虑资源的分配/归还操作、加上语言本身区分的比较严格,因此除非刻意攻击,否则引用计数的效率——无论空间还是时间效率——以及各方面的可靠性可控性都是无与伦比的。

不过,在其它语言中,由于GC的存在以及滥用,加上语言本身的限制,无效的引用就会特别多;那么对于无视引用添加/撤销、只会在时间到后算总账的GC,这并不会带来任何负面影响;可一旦用了引用计数,这种语言/语法习惯就会制造出海量的多余引用,就会大量消耗性能。


换句话说,在没有GC、习惯了手动资源分配回收的C++里面,用起来几乎不需要付出什么代价的引用计数是如此优越,以至于都抑制了给它添加GC的需求——引用计数是如此的好用、轻量,我们干嘛要引入一个难以完全控制的GC呢?


但在基于GC构筑的语言里,前面提到过,引用计数本就不能实现GC;哪怕勉强拿它实现了GC,实质上也相当于“GC之外额外加了一套引用计数”——那么对这些已经有了GC的语言,这就相当于把GC的工作通过引用计数分散到程序执行的每时每刻、使得GC执行时可以少检查一些对象:换句话说,付出了很多时间和空间代价,换来了GC执行时工作量的减少。

这在某些情况下的确有奇效。或者说,也可以精心构造一个GC+引用计数相对于GC有利的场景:比如不停的分配和归还新对象,引用计数可以确保这些对象可以第一时间被归还,不会过多占用内存——如果没有引用计数,那么GC执行前就会有海量内存得不到释放、且GC执行时需要遍历海量对象,需要“卡顿”更长时间才能完成释放。

换句话说,GC+引用计数方案有效降低了程序平时的内存空间占用量、降低了GC执行时world stop的必需时长。

但同样的,这种情况也不算多见(比起大量添加删除引用还是更常见一些),在正常场景下是得不偿失的。


最后,总结一下。

引用计数并不是GC;“引用计数实现GC”本质上是给一套常规GC附加了一套引用计数系统,多耗了执行时间,换来的好处却寥寥无几


引用计数的优点:

  • 低耗
  • 实时归还资源
  • 实现简单

引用计数的缺点:

  • 不是GC
  • 用起来没那么省心,需要自己注意,不能搞出循环引用


GC的优点:

  • 总体效率高
  • 用起来简单:申请资源,使用,然后忘掉就好

GC的缺点:

  • 内存消耗量大,本质上是一种“空间换便利”方案。GC执行前已经不用的对象占有的内存得不到释放,使得程序需要的最小内存常常会达到C/C++的两三倍甚至更高。
  • GC执行时会造成(可察或不可察的)卡顿,在压力极大时可能意外影响到服务质量
  • 不能实时归还资源,在操作某些需要及时归还的资源(如设备句柄)时必须设置专门的逻辑流程


GC+引用计数的优点:

  • 实时归还资源
  • GC执行时造成卡顿的可能更低、执行时间更短

GC+引用计数的缺点:

  • 额外消耗高
  • 额外消耗的时间/空间很难被它所节约的空间/时间弥补,尤其是时间,从总量上来说总是比单纯的GC更差


正是因为引用计数的种种优点,C++才选择了它(而不是GC);这是C系语言一贯的“相信程序员”思想的延续:语言设计者相信他们能用好这个难用的东西、写出效率最高的程序。

而其他程序选择了“尽量降低门槛”,因此才选择了GC,替程序员下了“空间换便利”的决定——对C++程序员来说,这是不可接受的。因为他们常常需要把机器性能挖到极致。


至于GC+引用计数(或者说“引用计数实现GC”)嘛……相对于它带来的一点点好处,付出的代价实在太重,因此几乎没有什么人会选择这个方案。


user avatar   xing-jiankuan 网友的相关建议: 
      

哪里有说过“引用计数在做 GC 时有性能问题”?

具体是什么性能问题?




  

相关话题

  golang 为什么没有三元运算符? 
  算法老师劝学生放弃学习 JavaScript,我该怎么办? 
  C++中 std::string 应该声明在循环内部还是外部? 
  JavaScript 关于 if…else if…else 的疑问? 
  C++,全局变量如果用new了,需要delete吗? 
  C++的CRTP所带来的静态多态功能具体有什么用? 
  为什么尤大说react的性能不如vue? 
  有哪些短小却令人惊叹的 JavaScript 代码? 
  C++ 和Java 的 double 类型都是 8 字节,为何 C++ 存不下 3.1415926 ? 
  为什么不建议一个对象在多处存储引用? 

前一个讨论
如何看待山西运城马戏团老虎失控咬人,马戏团是否应该被取缔?
下一个讨论
是否 MIUI 12 存在不修复 Bug,反而去做一些可有可无的功能的情况?为什么会这样?





© 2024-11-09 - tinynew.org. All Rights Reserved.
© 2024-11-09 - tinynew.org. 保留所有权利