Luke a Pro

Luke Sun

Developer & Marketer

🇺🇦
EN||

ARC 缓存 (Adaptive Replacement)

| , 4 minutes reading.

ARC 缓存:大战略家

算法背后的故事:两位顾问

想象一位国王(缓存),他的城堡空间有限。他有两位顾问:

  1. 实用主义者 (LRU): “留下那些最近来访的人!他们很活跃!”
  2. 历史学家 (LFU): “留下那些经常来访的人!他们很忠诚!”

多年来,国王们被迫只能选择一位顾问,而忽略另一位。 但在 2003 年,IBM 的研究人员提出了一套新系统:同时听取两人的意见,并惩罚那个犯错的人。

国王在城墙外维护着两个“幽灵名单”。这些名单记录了那些最近被驱逐的人的名字。

  • 如果一个在 LRU 幽灵名单上的人回来了,国王意识到:“我不该听历史学家的!我需要更多空间给新近项。” -> 他扩大了 LRU 的区域。
  • 如果一个在 LFU 幽灵名单上的人回来了,国王意识到:“我不该听实用主义者的!我需要更多空间给高频项。” -> 他扩大了 LFU 的区域。

这就是 ARC (自适应置换缓存)。它是一个能够实时自我调优的系统。

为什么需要它?

  • 数据库服务器: 工作负载是会变的。白天,用户可能在扫描最近的记录(LRU 模式)。到了晚上,备份任务可能会反复读取相同的旧数据块(LFU 模式)。ARC 无需重启即可自动适应。
  • ZFS 文件系统: ARC 因驱动了世界上最先进的文件系统之一 ZFS 而闻名。它为复杂的混合工作负载提供了极高的命中率。

算法是如何“思考”的

ARC 将缓存分为两个动态区域:

  • T1 (近况): 最近只见过一次的项。
  • T2 (频率): 至少见过两次的项。

以及两个幽灵区域(驱逐历史):

  • B1 (近况幽灵): 从 T1 被驱逐的项。
  • B2 (频率幽灵): 从 T2 被驱逐的项。

魔法逻辑:

  • p 为 T1 的目标大小。
  • 如果我们在 B1 中命中(意味着我们不该从 T1 驱逐它),我们 增加 p(给近况更多空间)。
  • 如果我们在 B2 中命中(意味着我们不该从 T2 驱逐它),我们 减少 p(给频率更多空间)。

这是一个反馈循环。缓存实际上是在从它自己的驱逐错误中学习

工程决策:专利之墙

在很长一段时间里,ARC 被 IBM 申请了专利,这阻止了许多开源项目(如 Linux 内核或 PostgreSQL)直接使用它。

  • 替代方案: 这导致了 LIRSClock-Pro 的发明,它们试图在不侵犯专利的情况下逼近 ARC 的天才构想。
  • 现状: 该专利大约在 2020 年过期,所以现在 ARC 可以被所有人免费使用了!

实现参考 (Python 概念版)

实现完整的 ARC 非常复杂(涉及 4 个链表!)。这里是其自适应逻辑的概念素描。

class ARCCache:
    def __init__(self, capacity):
        self.c = capacity
        self.p = 0  # T1 (近况) 的目标大小
        self.t1 = [] # 近况列表 (真实数据)
        self.t2 = [] # 频率列表 (真实数据)
        self.b1 = [] # 近况幽灵 (仅元数据)
        self.b2 = [] # 频率幽灵 (仅元数据)

    def access(self, key):
        # 情况 1: 缓存在 T1 或 T2 中命中
        if key in self.t1:
            self.t1.remove(key)
            self.t2.append(key) # 晋升到频率表
            return "Hit T1"
            
        if key in self.t2:
            self.t2.remove(key)
            self.t2.append(key) # 更新位置
            return "Hit T2"

        # 情况 2: 幽灵命中 (学习时刻)
        if key in self.b1:
            # 我们太早从 T1 驱逐它了!增加 T1 的大小。
            delta = 1
            if len(self.b1) < len(self.b2):
                delta = len(self.b2) / len(self.b1)
            self.p = min(self.c, self.p + delta)
            self.replace(key)
            # 从幽灵移回真实频率表
            self.b1.remove(key)
            self.t2.append(key)
            return "Ghost Hit B1 - 向近况调整"

        if key in self.b2:
            # 我们太早从 T2 驱逐它了!减少 T1 的大小 (偏向 T2)。
            delta = 1
            if len(self.b2) < len(self.b1):
                delta = len(self.b1) / len(self.b2)
            self.p = max(0, self.p - delta)
            self.replace(key)
            # 从幽灵移回真实频率表
            self.b2.remove(key)
            self.t2.append(key)
            return "Ghost Hit B2 - 向频率调整"

        # 情况 3: 完全未命中
        # 加入 T1 (新项)
        if len(self.t1) + len(self.b1) == self.c:
            if len(self.t1) < self.c:
                self.b1.pop(0)
                self.replace(key)
            else:
                self.t1.pop(0)
        
        self.t1.append(key)
        return "Miss"

    def replace(self, key):
        # 根据参数 'p' 决定杀掉谁
        if len(self.t1) > 0 and (len(self.t1) > self.p or (key in self.b2 and len(self.t1) == self.p)):
            old = self.t1.pop(0)
            self.b1.append(old) # 移入近况幽灵
        else:
            old = self.t2.pop(0)
            self.b2.append(old) # 移入频率幽灵

小结

ARC 教会我们:僵化的规则在动态的世界里注定失败。通过承认自己可能会犯错并记录自己的“遗憾”(幽灵名单),ARC 达到了一种纯 LRU 或纯 LFU 都无法企及的平衡。它提醒我们,真正的智慧标志不仅在于知道,更在于学习。