基于DAMON的回收

基于DAMON的回收 (DAMON_RECLAIM) 是一个静态内核模块,旨在轻度内存压力下用于主动且轻量级的回收。它并非旨在取代基于LRU列表的页粒度回收,而是根据不同的内存压力水平和需求进行选择性使用。

何时需要主动回收?

在通常的内存超配系统上,主动回收冷页有助于节省内存并减少因进程直接回收或kswapd的CPU消耗引起的延迟峰值,同时只产生极小的性能下降[1] [2]

空闲页报告[3] 基于内存超配的虚拟化系统是此类情况的一个很好的例子。在此类系统中,访客虚拟机向宿主机报告其空闲内存,宿主机将报告的内存重新分配给其他访客。结果,系统内存得到充分利用。然而,访客可能并不那么节省内存,这主要是因为某些内核子系统和用户空间应用程序被设计为尽可能多地使用可用内存。因此,访客可能只向宿主机报告少量空闲内存,导致系统内存利用率下降。在访客中运行主动回收可以缓解这个问题。

工作原理?

DAMON_RECLAIM 查找在特定时间段内未被访问的内存区域并将其换出。为避免换出操作消耗过多CPU,可以配置一个速度限制。在速度限制下,它会优先换出长时间未被访问的内存区域。系统管理员还可以配置在何种情况下,该方案应根据三个内存压力水位线自动激活和停用。

接口:模块参数

要使用此功能,您首先应确保您的系统正在运行一个使用CONFIG_DAMON_RECLAIM=y构建的内核。

为了让系统管理员能够启用或禁用它并针对给定系统进行调整,DAMON_RECLAIM 利用了模块参数。也就是说,您可以在内核启动命令行上添加damon_reclaim.<parameter>=<value>,或者将适当的值写入/sys/module/damon_reclaim/parameters/<parameter>文件。

以下是每个参数的描述。

enabled (启用)

启用或禁用 DAMON_RECLAIM。

您可以通过将此参数的值设置为Y来启用 DAMON_RECLAIM。将其设置为N将禁用 DAMON_RECLAIM。请注意,由于基于水位线的激活条件,DAMON_RECLAIM 可能无法进行实际的监控和回收。有关此内容的详情,请参阅下方水位线参数的描述。

commit_inputs (提交输入)

使 DAMON_RECLAIM 再次读取输入参数,但除了enabled之外。

DAMON_RECLAIM 运行时更新的输入参数默认不生效。一旦此参数设置为Y,DAMON_RECLAIM 将再次读取除enabled之外的所有参数值。重新读取完成后,此参数将设置为N。如果在重新读取时发现无效参数,DAMON_RECLAIM 将被禁用。

min_age (最小生存期)

用于识别冷内存区域的时间阈值,以微秒为单位。

如果一个内存区域在此时间或更长时间内未被访问,DAMON_RECLAIM 会将该区域识别为冷区域并进行回收。

默认为 120 秒。

quota_ms (时间配额)

回收操作的时间限制,以毫秒为单位。

DAMON_RECLAIM 尝试在一个时间窗口 (quota_reset_interval_ms) 内,最多使用此时间来尝试回收冷页。这可以用于限制 DAMON_RECLAIM 的 CPU 消耗。如果值为零,则此限制被禁用。

默认为 10 毫秒。

quota_sz (大小配额)

回收操作的内存大小限制,以字节为单位。

DAMON_RECLAIM 会在一个时间窗口 (quota_reset_interval_ms) 内统计其尝试回收的内存量,并确保不超过此限制。这可以用于限制 CPU 和 IO 的消耗。如果此值为零,则此限制被禁用。

默认为 128 MiB。

quota_reset_interval_ms (配额重置间隔)

时间/大小配额的费用重置间隔,以毫秒为单位。

也就是说,DAMON_RECLAIM 在 quota_reset_interval_ms 毫秒内,不会尝试回收超过 quota_ms 毫秒的时间或 quota_sz 字节的内存。

默认为 1 秒。

quota_mem_pressure_us (内存压力微秒配额)

内存压力停顿时间的期望级别,以微秒为单位。

在保持其他配额设定的上限的同时,DAMON_RECLAIM 自动增减配额的有效级别,以期达到此内存压力水平。系统范围的some内存PSI(每配额重置间隔quota_reset_interval_ms内的微秒数)会被收集并与此值进行比较,以查看是否满足目标。值为零表示禁用此自动调整功能。

默认禁用。

quota_autotune_feedback (配额自动调整反馈)

用于自动调整有效配额的用户可指定反馈。

在保持其他配额设定的上限的同时,DAMON_RECLAIM 自动增减配额的有效级别,以期从用户那里收到值为10,000的反馈。DAMON_RECLAIM 假定反馈值和配额成正比。值为零表示禁用此自动调整功能。

默认禁用。

wmarks_interval (水位线检查间隔)

当 DAMON_RECLAIM 已启用但因其水位线规则而处于非活动状态时,检查水位线前的最小等待时间。

wmarks_high (高水位线)

高水位线的空闲内存率(每千分比)。

如果系统每千字节的空闲内存量高于此值,DAMON_RECLAIM 将变为非活动状态,因此它不执行任何操作,只定期检查水位线。

wmarks_mid (中水位线)

中水位线的空闲内存率(每千分比)。

如果系统每千字节的空闲内存量介于此值和低水位线之间,DAMON_RECLAIM 将变为活动状态,从而开始监控和回收。

wmarks_low (低水位线)

低水位线的空闲内存率(每千分比)。

如果系统每千字节的空闲内存量低于此值,DAMON_RECLAIM 将变为非活动状态,因此它不执行任何操作,只定期检查水位线。在这种情况下,系统将回退到基于LRU列表的页粒度回收逻辑。

sample_interval (采样间隔)

监控的采样间隔,以微秒为单位。

DAMON 用于冷内存监控的采样间隔。详情请参阅 DAMON 文档 (详细用法)。

aggr_interval (聚合间隔)

监控的聚合间隔,以微秒为单位。

DAMON 用于冷内存监控的聚合间隔。详情请参阅 DAMON 文档 (详细用法)。

min_nr_regions (最小监控区域数)

最小监控区域数。

DAMON 用于冷内存监控的最小监控区域数。这可以用于设置监控质量的下限。但是,将其设置得太高可能会导致监控开销增加。详情请参阅 DAMON 文档 (详细用法)。

max_nr_regions (最大监控区域数)

最大监控区域数。

DAMON 用于冷内存监控的最大监控区域数。这可以用于设置监控开销的上限。然而,将其设置得太低可能会导致监控质量不佳。详情请参阅 DAMON 文档 (详细用法)。

monitor_region_start (目标内存区域起始物理地址)

目标内存区域的起始物理地址。

DAMON_RECLAIM 将对其进行操作的内存区域的起始物理地址。也就是说,DAMON_RECLAIM 将在此区域中查找冷内存区域并进行回收。默认情况下,最大的系统RAM用作该区域。

monitor_region_end (目标内存区域结束物理地址)

目标内存区域的结束物理地址。

DAMON_RECLAIM 将对其进行操作的内存区域的结束物理地址。也就是说,DAMON_RECLAIM 将在此区域中查找冷内存区域并进行回收。默认情况下,最大的系统RAM用作该区域。

skip_anon (跳过匿名页)

跳过匿名页回收。

如果此参数设置为Y,DAMON_RECLAIM 不会回收匿名页。默认情况下为N

kdamond_pid (DAMON线程PID)

DAMON 线程的 PID。

如果 DAMON_RECLAIM 已启用,此参数将变为工作线程的 PID。否则,为 -1。

nr_reclaim_tried_regions (尝试回收的内存区域数)

DAMON_RECLAIM 尝试回收的内存区域数量。

bytes_reclaim_tried_regions (尝试回收的内存区域总字节数)

DAMON_RECLAIM 尝试回收的内存区域总字节数。

nr_reclaimed_regions (成功回收的内存区域数)

DAMON_RECLAIM 成功回收的内存区域数量。

bytes_reclaimed_regions (成功回收的内存区域总字节数)

DAMON_RECLAIM 成功回收的内存区域总字节数。

nr_quota_exceeds (配额超出次数)

时间/空间配额限制被超出的次数。

示例

以下运行时示例命令使 DAMON_RECLAIM 查找 30 秒或更长时间未访问的内存区域并将其换出。回收操作限制为每秒最多 1 GiB,以避免 DAMON_RECLAIM 在换出操作中消耗过多 CPU 时间。它还要求 DAMON_RECLAIM 在系统空闲内存率超过 50% 时不执行任何操作,但当其低于 40% 时开始实际工作。如果 DAMON_RECLAIM 未能取得进展,导致空闲内存率低于 20%,它会再次要求 DAMON_RECLAIM 不执行任何操作,以便我们可以回退到基于 LRU 列表的页粒度回收机制。

# cd /sys/module/damon_reclaim/parameters
# echo 30000000 > min_age
# echo $((1 * 1024 * 1024 * 1024)) > quota_sz
# echo 1000 > quota_reset_interval_ms
# echo 500 > wmarks_high
# echo 400 > wmarks_mid
# echo 200 > wmarks_low
# echo Y > enabled