性能基准
测试环境:Apple M5(10 核),本地回环,使用 redis-benchmark -n 100000 -c 50。
| 场景 | SET QPS | GET QPS | p50 延迟 |
|---|---|---|---|
| 基线(不淘汰) | 62,189 | 65,231 | 0.42ms |
流水线 -P 16 | 350,877 | 378,787 | 1.06ms |
| LFU(16MB 上限) | 57,339 | 61,690 | 0.42ms |
| AHE(16MB 上限) | 59,559 | 50,787 | 0.42ms |
如何解读
- 流水线 是提升吞吐最显著的杠杆 —— 批量发送命令可降低单请求开销,成倍提升吞吐。
- QPS 和延迟只说明引擎"跑得多快",却无法体现淘汰算法的真正目的:把工作集留住。下面的命中率表补上了这个缺口。
各淘汰策略的命中率
这才是评判一个缓存淘汰算法的核心指标,也是纯 QPS 数字无法体现的:在真实访问模式下,工作集能留住多少。测试以端到端方式跑在真实 server 上(与上面的 QPS 方法一致),工作集为 100,000 个不同键,缓存上限 5,000 个条目(约 590 KiB)——即缓存只装得下键空间的 1/20,淘汰始终处于高压状态。
| 策略 | zipf(稳定倾斜) | shift(热点轮转) | mixed(热集 + 扫描) | scan(纯顺序) |
|---|---|---|---|---|
allkeys-lru | 59.5% | 52.4% | 56.3% | 0.0% |
allkeys-lfu | 59.4% | 51.1% | 58.0% | 0.0% |
allkeys-ahe | 59.5% | 52.3% | 56.8% | 0.0% |
allkeys-random | 57.1% | 52.4% | 54.5% | 0.0% |
越高越好。每个格子用同一份固定种子的负载、对应当前策略的全新 server 回放;数值取自 server 自身的 keyspace_hits / (keyspace_hits + keyspace_misses)(含冷启动)。复现方式:scripts/bench-hit-ratio.sh;测试代码见 examples/hit_ratio_bench.rs。
方法说明
- 读穿透客户端:
GET未命中则用SET回填,与实际缓存填充一致。只有缓存填满后淘汰才会触发——这才是关键区间。 - 真实的请求间隔(约 1ms)。FerrumKV 的 LRU 与 AHE 都是时间相关的:recency 以 600 秒为尺度衡量,AHE 控制器也以观测到的命中率为输入。紧凑的进程内微基准会在毫秒内跑完,把 recency 信号压平、让自适应循环失效——那样 AHE 会静默退化为纯 LFU,对比也就失去意义。用真实 server 加真实间隔驱动,recency 与自适应循环才能表现得和生产环境一致。
- 每个
(策略, 模式)都用独立的新 server,计数不会跨负载泄漏。 - 负载模式:
zipf(稳定 Zipf 倾斜,最简单的情况)、shift(热点区间每个 epoch 轮转,非平稳负载)、mixed(小热集 + 周期性全扫描,类 OLTP)、scan(纯顺序,用于诚实展示"所有策略都无能为力"的对照)。第五个模式ttl也可通过--patterns ttl选用,它把持久热集与短 TTL 的 ephemeral 集混合以检验 TTL 感知型淘汰;在我们的实测中,它在现实缓存容量下与 LRU/LFU 趋同、在极紧的缓存下也不稳定优于 LFU,因此作为 opt-in 模式,不列入上面的头条矩阵。 - 单次代表性运行;数字在不同运行间约有 ±1 个百分点的波动,因为引擎内部的 LFU/LRU 采样 RNG 以系统时钟为种子。
数字说明了什么
- 稳定倾斜(
zipf):各策略都收敛到约 59%;AHE 与领先者持平。 - 热点轮转(
shift):LFU 粘性的频率计数器崩到 51.1%,而 LRU 与 AHE 维持在 约 52.3–52.4%——AHE 跟随 LRU,避开了 LFU 的最差情况。 - 混合(
mixed):LFU 领先(58.0%),LRU 跌到 56.3%;AHE 落在 56.8%——接近 LRU,明显优于random的 54.5% 下限。 - 纯扫描(
scan):所有策略都被钉在约 0%,证明 AHE 不会把无望的访问模式变魔术般救回来。
结论:AHE 是"无后悔"的选择。 在每种负载下它都跟住 LRU 与 LFU 中较优的一方,且从不陷入任一方的最差情形(LFU 在热点轮转时、LRU 在扫描密集的混合负载时)。这种自适应能力——而非对 recency 或 frequency 的固定偏向——才是该算法的意义所在。
完整的方法说明与原始输出见 benches/redis-benchmark.md。