首页
社区
课程
招聘
[翻译]V8 引擎在其并行 GC 管理器 Orinoco 中实现了一个新特性 - Scavenger
2018-1-14 16:33 4185

[翻译]V8 引擎在其并行 GC 管理器 Orinoco 中实现了一个新特性 - Scavenger

2018-1-14 16:33
4185

V8 引擎在其并行 GC 管理器 Orinoco 中实现了一个新特性 - Scavenger


 

V8中的JS对象分配在由V8的GC管理的堆上。 在之前的博文中,我们已经谈到了如何减少垃圾回收的暂停时间和内存消耗。 在这篇博文中,我们介绍并行Scavenger,Orinoco的最新功能,V8的主要并发和并行GC,并讨论设计决策和我们在实现途中的替代方法。

 

V8将托管的堆分成几代,最初是在年轻generation的“nursery”中分配对象。 在垃圾回收之后,对象被复制到中间generation,但这个仍然是年轻的generation的一部分。 经过了的另一个垃圾回收之后,这些对象被移动到旧一代(见图1)。 V8实现了两个GC:一个是频繁收集generation的GC,另一个是收集整个堆的GC包括年轻的和老的在内。 Old-to-young generation的引用是年轻generation GC的根。 记录这些引用可以在移动对象时提供有效的根识别和引用更新。

 

图片描述

 

由于年轻generation相对较小(V8中为16MB),它会被对象迅速的占满,需要经常进行回收。
在M62之前,V8使用了Cheney半空间复制GC(见下文),将年轻generation分为两部分。
在JavaScript执行期间,只有一半的年轻generation可用于分配对象,而另一半则保持空白。
在进行年轻的垃圾回收过程中,活动对象会从一半复制到另一半,即时压缩内存。
已经被复制过的实时对象被认为是中间generation的一部分,并被提升到老generation。

 

从M62开始,V8将收集年轻generation的默认算法变成并行Scavenger,类似于Halstead的半空间复制GC,不同之处在于V8使用动态来代替跨线程静态工作。
下面我们解释三种算法:a)单线程Cheney半空间复制GC,b)并行Mark-Evacuate方案,c)并行Scavenger。

单线程Cheney半空间复制

在M62之前,V8使用了单线程Cheney半空间复制算法,它非常适合单核执行和分代方案。
在年轻generation收集之前,两个半空间的内存都被提交并分配了适当的标签:包含当前对象集合的页面被称为from-space,而对象被复制到的页面被称为to-space。

 

清扫者认为调用堆栈中的引用以及从旧到新的引用是根。图2解释了算法,其中Scavenger最初扫描这些根,并复制from-space中尚未复制的可达对象到to-space中。那些经过GC幸存下来的对象被提升(移动)到旧generation。在扫描根和第一轮复制之后,扫描新分配的to-space中的对象以供参考。同样,所有提升的对象都将被扫描以查找from-space中的新引用。这三个阶段在主线程中交错进行。该算法持续到to-space或老generation中不再有新的可达对象为止。 此时,to-space仅包含不可达对象,即它只包含垃圾。

 

图片描述

并行Mark-Evacuate方案

主要优势是利用了已有的GC基础设施:Mark-Sweep-Compact收集器。
该算法由三个阶段组成:标记,复制和更新指针,如图3所示。
为了避免使用年轻generation中的大量页面维护空闲列表,年轻generation仍然使用半空间,通过在垃圾收集过程中将活动对象复制到to-space中来保持紧凑。年轻一代最初是平行的。标记后,活动对象被平行复制到相应的空间,工作是基于逻辑页面分配的。参与复制的线程保留自己的本地分配缓冲区(LAB),这些缓冲区在完成复制时被合并。在复制之后,应用相同的并行化方案来更新对象间指针。这三个阶段是锁步(lockstep)执行的,也就是说,虽然阶段本身是并行执行的,但是线程必须在继续下一个阶段之前进行同步。

 

图片描述

并行Scavenger

并行Mark-Evacuate收集器分离计算活跃度,复制活动对象和更新指针的阶段。一个明显的优化是合并这些阶段,设计一个算法,同时标记,复制和更新指针。通过合并这些阶段,我们实际上得到了V8使用的并行Scavenger,它与Halstead的半空间收集器类似,不同之处在于V8使用动态工作和一个简单的负载平衡机制来扫描根(见图4)。就像单线程Cheney算法一样,它们的阶段是:扫描根,在年轻generation中复制,向老generation推广,更新指针。
我们发现,大部分的根通常是从老generation到年轻generation的参照。在我们的实现中,每个页面都维护着记录集,这些记录集在GC线程之间自然分配了根,然后对象被并行的进行处理。新发现的对象被添加到GC线程可以获取的全局工作表。该工作表提供了快速任务本地存储以及全局存储共享工作。当当前处理的子图不适合工作(例如线性链对象)时,屏障会确保任务不会过早终止。每个任务中的所有阶段都在并行执行并且是交错执行,最大限度地提高了工作任务的利用率。

 

图片描述

结论

Scavenger算法最初设计为单核性能最佳的算法。从那时起,一切就都变了。即使在低端移动设备上,CPU核心也往往很强劲。更重要的是,这些核心通常都是正常运行的。 为了充分利用这些核心,V8的垃圾收集器Scavenger的最后一个顺序组件必须进行现代化。

 

平行Mark-Evacuate GC的一大优点是可以提供确切的活动性信息。这个信息可以 通过例如移动和重新连接包含大部分活动对象的页面来避免复制。 然而在实际上,这些在基准测试中可以观察到的东西,却很少在真实的网站上显示出来。 平行Mark-Evacuate收集器的缺点是执行三个独立锁步阶段时的开销。 当GC在非活跃对象占大多数的堆上调用时,这种开销尤其明显,许多真实世界的网页就是这种情况。 请注意,在大多数非活跃对象堆上调用垃圾回收实际上是理想的情况,因为垃圾回收通常受到活跃对象大小的限制。

 

并行Scavenger通过在小堆或是几乎为空的堆上提供优化的Cheney算法来弥补这一性能差距,同时仍然提供高吞吐量,以防堆积物变大。
图片描述

 

V8现在配备了并行Scavenger,通过大量基准测试我们发现它能够减少主线程垃圾收集总时间的20%-50%。
图5显示了各种现实世界网站实现的比较,展示出大约有55%的性能改进。类似的改进可以通过观察最大和平均暂停时间。如果你想知道接下来会发生什么,请继续关注我们。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工 作,每周日13:00-18:00直播授课

最后于 2019-2-1 16:10 被admin编辑 ,原因:
收藏
点赞1
打赏
分享
最新回复 (1)
雪    币: 32401
活跃值: (18875)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
kanxue 8 2018-1-14 17:35
2
0
感谢分享!
游客
登录 | 注册 方可回帖
返回