缓存Caffeine与Sieve比较


Caffeine缓存作者:

背景信息
Caffeine 使用自适应窗口技术,因此不会完全影响您的观察结果。论文建议将 1%的窗口作为起点,因为这在数据库、搜索和分析等许多关键工作负载中都很有效。论文最后指出,有些工作负载需要更大的窗口,而动态调整窗口则是未来工作的重点。后续评估方法的工作似乎确实纠正了这一不足,并在实践中得到了广泛应用(可用于 Java、Rust、Go、Python 和 TypeScript)。

Caffeine 从 1% 的窗口开始,监测 TinyLFU 采样期间的命中率,并使用爬坡法调整窗口大小。频率计数器的数量事先并不知晓,因此就像哈希表一样,容量会根据条目数增长。这种大小调整会损失流行度得分,政策可能会从较差的窗口大小开始。

因此,就像 JIT 编译器一样,在预热期间,命中率可能会较低,但很快就会达到峰值性能,因此,除了极短的运行时间外,这就变成了噪音。由于为缓解散列泛洪攻击而引入的抖动,不同运行之间的接纳决定会略有不同。

分析
我通过比较 LRU 命中率并排运行两个模拟器,以确保正确评估跟踪。Caffeine 的模拟器没有丰富的可变大小支持(条目的权重),因为这在 Java 中不太常见,因为对象的堆使用情况大多对开发人员隐藏。有一个分叉扩展了支持,以评估大小感知的 TinyLFU 策略(研究论文),他们提出了一种适应措施以进一步提高命中率。我希望有一天能够评估该提案并纳入改进。

我在0f4d135 (当前主版本)上使用了 libcachesim,在[url=https://github.com/1a1a11a/libCacheSim/tree/0f4d13539646e2ff1f69789d0c829d4b58e354ce]cb5f75d[/url] (当前主版本)上使用了 Caffeine,并在此补丁中包含了新的跟踪格式。
在某些跟踪中,模拟器报告 FIFO 和 LRU 的不同结果最多可达 0.25%。我确定这是因为 libcachesim 不会在缓存命中时更新条目的大小,但会用新的权重记录字节命中。

                                Hit Rate        Byte Hit Rate
FIFO    36.64% (63.36% MR)    26.77% (73.23% MR)
LRU    36.96% (63.04% MR)    27.14% (72.86% MR)
QDLP    43.43% (56.57% MR)    35.99% (64.01% MR)
S3Fifo    38.22% (61.78% MR)    29.10% (70.90% MR)
Sieve    43.47% (56.53% MR)    36.03% (63.97% MR)
Caffeine    47.07% (52.93% MR)

Caffeine以 3.60% 的 HR / 2.19% 的 BHR 领先 Sieve(最佳算法),以 8.85% 的 HR / 9.12% 的 BHR 领先 S3Fifo。

多线程
文章称,LRU 会因锁定下的重新排序而成为可扩展性瓶颈。这种说法可能会引起误解,因为许多高速缓存会将其驱逐策略与管理并发的方式分离开来,从而使 LRU 的特性变得无关紧要。

这样,缓存设计者就可以考虑采用更广泛的算法和数据结构,从而提高时间和空间效率,或实现更多功能。

例如,Caffeine 在 16 个内核(2015 年硬件)上的线性扩展速度为 3.8 亿读取/秒。相比之下,Segcache 的 FIFO 策略在 24 个内核(2020 年硬件)上的读取速度为 7000 万次/秒。虽然基准可能有所不同,但这表明策略的并发性并不需要成为瓶颈,设计人员可以专注于其他方面的优化。

结论
在本项目视为典范并用于策略设计的工作负载中,我们可以看到自适应 W-TinyLFU (Caffeine) 表现出了极具竞争力的性能。比较参考实现以避免意外差异非常重要,我没有试图调试你的实现来解释性能下降的原因。我相信任何静态配置在某些情况下都会表现不佳,设计者应继续探索如何做出更有效的自适应选择。


==================================================
 
网友讨论:
既然我已经收集了很多采用它的尝试,也请允许我谈谈自己的看法。

1、首先,我尝试在散列表和无锁队列上实现缓存,就像作者写的那样。我很恼火,s3-fifo 确实比在哈希表上使用全局互斥实现的 lru 缓存快了很多倍,但还是输给了使用 bp-wrapper 技术的 ristretto 和 theine。

剖析器还显示,驱逐策略花费了大量时间。我只有在重写了使用 bp-wrapper 的实现后,才能击败 ristretto 和 theine。

2、实际上,命中率的大幅跳变是由 ghost queue 的实现方式不同造成的。在论文中,作者建议继续在幽灵队列中存储项目(这是 redpanda 现在的做法,我也决定尝试这样做)。根据算法,幽灵队列的大小受限于主队列的大小,这似乎是造成命中率大幅跳变的原因。缓存容量越大,小队列的容量就越大 => 我们存储项目的时间越长 => 进入主队列的项目越多 => 幽灵队列存储的项目也越多。

最后我得出的结论是,存储小队列容量 + 主队列容量 + 幽灵队列容量 = 0.1 个项目容量 + 0.9 个项目容量 + 0.9 个项目容量 = 1.9 个项目容量,这太作弊了。

因此,我决定尝试在幽灵队列中存储项目的哈希值,并按照 fifo 顺序驱逐它们。结果很沮丧。

将幽灵队列的大小限制在主队列的范围内,在缓存规模较小的情况下效果很差。

我今晚得到的最好结果是将幽灵队列限制为整个缓存的大小,结果如下:r.txt.在除 S3 和 DS1 之外的所有跟踪上,S3-FIFO(稍作修改)的性能实际上都优于或大致等于 W-TinyLFU,且没有太大波动。

总之,我完全不同意 S3-FIFO 相对于其他策略的可扩展性优势。就命中率而言,S3-FIFO 确实经常优于 W-TinyLFU,但在对 lfu 友好的轨迹上却感觉很糟糕。我认为 S3-FIFO 有生存的权利,尤其是如果有人能利用它做出更适应性更强的东西,但放弃一切并在 W-TinyLFU 上重写一切的想法正变得越来越诱人