缓解重新定位降级以改善缓存局部性算法
2022 年 7 月 1 日此处展示的工作是作为 Oracle、乌普萨拉大学和 KTH 之间的联合研究项目 的一部分进行的。关注 inside.java 上的博客系列,以阅读更多关于 Oracle 斯德哥尔摩开发办公室进行的 JVM 研究的信息。
嗨!我是 Jinyu Yu,一名主修嵌入式系统专业的 KTH 学生。2021 年期间,Oracle 给了我一个 绝佳机会,让我撰写硕士论文,重点关注低延迟垃圾回收器 ZGC。在导师 Tobias Wrigstad、Per Lidén 和 Erik Österlund 的宝贵指导下,我成功地在 ZGC 上实施了一项重新定位性能改进。
缓存局部性对于程序的性能至关重要。已经提出了许多算法来提高缓存局部性,旨在提高程序的吞吐量。然而,局部性改进的过程并不总是轻松的 - 有时缺点甚至可能超过优点,并降低整体性能。在我的论文 “通过识别小对象的大小来改善 ZGC 中的重新定位性能” 中,我分析了一个局部性改进算法 HCSGC 的缺点,并提出了重新定位改进措施来缓解这一问题。
缓存局部性的重要性:原因和时机
你可能已经知道,与 CPU 缓存相比,内存的速度要慢得多。更糟糕的是,存储在内存中的任何数据在由 CPU 处理之前都需要加载到缓存中。每次内存获取都有一个固定大小(缓存行的大小,通常为 64 字节),这使得一次加载多个小对象成为可能。将倾向于一起访问的对象放在同一缓存行中可以带来良好的缓存局部性。通过这样做,我们可以使用更少的内存操作加载对象,从而提高性能。
考虑三个小对象 A、B 和 C,它们将按顺序访问。当所有三个对象都落在不同的缓存行中时,我们会获得最差的缓存局部性。在这种情况下,需要三次内存访问才能获取所有这些对象。通过更好的局部性,这些对象可能会落在同一缓存行中,因此可以一次加载。它们有可能落在相邻的缓存行中,但我们仍然可以在硬件预取器的存在下获得接近前一种情况的良好性能。大多数时候启用的下一行硬件预取器会在内存访问发生时自动获取下一缓存行。因此,微小对象的更好局部性很可能带来更好的性能。
将大型对象放在一起时,您将不会获得好处。现在考虑我们有一个更大的 B,在这种情况下,一个包含 128 个整数的数组,它将占用超过 500 字节的内存,因此无法放在一个高速缓存行中。这三个对象将以类似的方式访问,A、B[50]、C。最佳和最差局部性分别显示在上图和下图中。由于 B[50] 远离 B 的两个边界,无论我们如何接近放置这三个对象,我们需要三个内存访问来获取这三个对象。
提高高速缓存局部性:方法和缺点
现在我们知道高速缓存局部性对于小对象很重要。提高高速缓存局部性的关键点是找到将一起访问的对象,或者换句话说,获取对象的访问模式。
ZGC 如何工作
在深入了解高速缓存局部性之前,让我们快速浏览一下 ZGC 的一些基本思想。ZGC 有三种不同大小的页面,分别称为小页面、中页面和大页面。对象根据其大小放置在不同的页面上。
页面大小类 | 对象大小 |
---|---|
小 | [0, 256] KB |
中 | (256 KB, 4MB] |
大 | >4MB |
一个 ZGC 周期有三个并发阶段。在标记/重新映射阶段,ZGC 执行对象遍历以标记所有活动对象,就像其他 GC 所做的那样。同时,为下一个阶段记录每个页面的活动信息。然后,在疏散候选(表示为 EC)阶段,活动字节比率低于 75% 阈值的页面将被标记为 EC。在最后一步,重新定位阶段,EC 中的所有活动对象将重新定位到新页面。旧页面以及它们上的死对象将被释放。
HCSGC 如何工作
HCSGC 提出两种主要方法来提高高速缓存局部性:扩大 EC 和延迟重新定位。HCSGC 或热冷对象隔离 GC,顾名思义,它试图通过识别对象的热度信息来提高局部性。在 ZGC 中,只有相对较小的一部分页面将被选为 EC 并稍后重新定位,但需要更多的重新定位来操作局部性。为了增加重新定位的数量,在 HCSGC 中,具有许多热对象的页面也将被选为 EC。如果一个对象自上次 GC 以来被访问过,则认为它很热。考虑到再次被访问的可能性,很明显,在提高局部性时,热对象比冷对象受益更多。
在 HCSGC 中,之前的第三阶段,重新定位阶段,被移动到下一个 GC 周期的开始,称为延迟重新定位。在两个 GC 周期中,变异器将在访问对象时重新定位对象。这使得在两个 GC 周期之间根据变异器的访问模式放置对象成为可能。
缺点
扩大 EC 和延迟重新定位都会以降低重新定位性能为代价来提高局部性。扩大 EC 会减慢重新定位,因为选择了更多页面进行移动。因此,选择更有价值的页面至关重要。HCSGC 尝试通过选择热且小的对象来有效地增加重新定位,其中热表示自上次 GC 以来访问过该对象,小表示该对象放在小页面上。但是,小页面不够小。ZGC 中的小页面可以容纳高达 256 KB 的对象,但只有可以放在几个高速缓存行(最多几百个字节)中的对象才能从重新定位中受益。结果,许多较大的对象将被移动,从而给内存带来沉重负担,而不会增加吞吐量。
惰性重定位将以两种方式导致回归。首先,HCSGC 中的重定位将与其他工作负载竞争,因为它是由变异器线程而不是并发 GC 线程处理的。其次,延迟重定位阶段将导致垃圾保留更长时间,从而使内存更加碎片化。这意味着更高的内存使用率和可能的 GC 计数增加。在受内存限制的环境中,碎片化内存将极大地增加 GC 计数并降低性能。
重定位改进
方法论
现在,我们面临 HCSGC 带来的三个回归。(1)重定位(相对)较大的对象会导致内存负荷过重。(2)重定位与变异器竞争。(3)内存碎片化。
在不损害局部性的情况下减少重定位量是减轻前两个回归的关键。一种直接的方法是在重定位对象时添加一个 if 子句来检查对象大小。但是,这无法解决内存碎片化问题 - 惰性重定位是基于页面的,我们不能只惰性重定位页面的部分。然后出现了另一个想法:为什么不把较大和较小的对象放入不同的页面中?通过这样做,我们只需要在“较小”的小页面上启用 HCSGC,而在“较大”的小页面上禁用它,那么三个回归都可以同时得到缓解。
因此,我们为 ZGC 引入了第 4 个页面类,即微型页面。之前的较小页面(0~256KB)将被划分为新的微型页面(0-256B)和新的较小页面(256B~256KB)。HCSGC 将在除微型页面之外的所有页面类中禁用。因此,足够小的对象有机会针对局部性改进进行优化,而所有从这种改进中永远受益较大的对象将保持香草 ZGC 行为,保持高并发性。
页面大小类 | 对象大小 |
---|---|
微型 | [0, 256] 字节 |
小 | (256B, 256KB] |
中 | (256 KB, 4MB] |
大 | >4MB |
好处
重定位改进主要集中在那些不会从四处移动中受益的对象上。考虑一个有大量 1K 字节对象程序。在原始 HCSGC 实现中,尽管重定位不会提高性能,但所有这些对象都将被放置在较小的页面中并进行惰性重定位。在受 CPU 限制的环境中,移动此类对象可能会给系统带来沉重负担并减慢程序速度。在受内存限制的环境中,内存碎片化将增加 GC 计数并降低性能。相反,在我们的设计中,这些对象将被放置在 HCSGC 被禁用的新较小页面上。它们将在并发 GC 线程中进行重定位,并且不再与变异器竞争。
展示
最好的结果之一来自 JGraphT 基准的最大团算法。我们将内存大小限制为 512MB,以模拟受内存限制的环境。在此测试中,HCSGC 比香草 ZGC 提高了 46.31%;通过我们的重定位改进,我们比原始 HCSGC 提高了 3.4%。
配置 | 平均值 | 标准差 | 相对标准差 | 性能 |
---|---|---|---|---|
HCSGC | 138656.00 | 930.11 | 0.67% | 0.00% |
微型页面 | 133948.00 | 1284.04 | 0.96% | 3.40% |
ZGC | 202871.33 | 11064.45 | 5.45% | ‐46.31% |