← 中文资源

简体中文版本

Functools LRU 缓存容量调优

用 functools.lru_cache 加速纯查表函数:maxsize 按工作集规模设定、cache_info 观测命中率、不可哈希参数需先转元组。

阅读英文原文

示例说明

lru_cache 是标准库里最廉价的提速手段:装饰一行,纯函数的重复调用直接命中缓存。maxsize 的选择决定效果——工作集大于容量时 LRU 频繁淘汰,命中率塌方;cache_info 返回的命中与未命中计数是调优的唯一直接证据,容量翻倍后命中率是否上升由数据说话。

技术要点

  • maxsize 应覆盖实际工作集规模
  • cache_info 的命中率是调优依据
  • 列表参数转元组才可哈希缓存

代码解读

price_of 装饰后查表免于重复执行;bundle_price 的参数声明为元组——列表不可哈希会直接 TypeError,这是最常见的接入坑。主循环对六十个键查三轮:首轮全未命中,后两轮全命中。cache_report 汇总命中率与当前占用,cache_clear 演示失效手段。

使用建议

有副作用的函数绝不能上 lru_cache,缓存会吞掉副作用。按调用方分布做容量规划,长尾键可拆独立缓存。跨进程缓存换 Redis,进程内缓存就用它。

代码示例

import functools
import time


def load_catalog():
    return {f'SKU-{i}': {'price': i * 3, 'stock': i % 7} for i in range(500)}


CATALOG = load_catalog()


@functools.lru_cache(maxsize=256)
def price_of(sku: str) -> int:
    # 纯查表函数:缓存命中即免重复计算
    return CATALOG[sku]['price']


@functools.lru_cache(maxsize=64)
def bundle_price(skus: tuple[str, ...]) -> int:
    # 参数必须是可哈希的:列表要转元组
    return sum(price_of(s) for s in skus)


def cache_report():
    info = price_of.cache_info()
    total = info.hits + info.misses
    hit_rate = info.hits / total if total else 0.0
    return {
        'hit_rate': round(hit_rate, 3),
        'hits': info.hits,
        'misses': info.misses,
        'maxsize': info.maxsize,
        'currsize': info.currsize,
    }


skus = [f'SKU-{i}' for i in range(60)]
start = time.perf_counter()
for _ in range(3):
    for sku in skus:
        price_of(sku)
elapsed = time.perf_counter() - start

print(cache_report())
print(f'60 keys x 3 rounds in {elapsed * 1000:.1f}ms')

# 命中率下降说明工作集超过 maxsize:调大或按业务键分布重新评估
price_of.cache_clear()