给递归fib加上@lru_cache,对比缓存命中、缓存刚清空后的首次调用、完全不用缓存三种情况的调用次数和耗时,看缓存到底省在哪。
不改函数体一个字,只加@lru_cache。
代码from functools import lru_cache @lru_cache(maxsize=128) def fib_cached(n): if n < 2: return n return fib_cached(n-1) + fib_cached(n-2) def fib_plain(n): if n < 2: return n return fib_plain(n-1) + fib_plain(n-2)
同样算fib(30),两个函数体逻辑完全一样。
结果fib_cached(30) = 832040, 调用次数: 31, cache_info: CacheInfo(hits=28, misses=31, maxsize=128, currsize=31) fib_plain(30) 调用次数: 2692537
差了86856倍——朴素递归每算fib(n)都要重新展开fib(n-1)和fib(n-2)整棵子树,指数级增长;加了@lru_cache之后,fib_cached(28)这类已经算过的中间结果被缓存住了,第二次需要它时(比如算fib(29)=fib(28)+fib(27)时的fib(28)),直接从缓存里取,不会再往下展开子树——这正是把指数级递归压成线性递归的经典手段(memoization),misses=31恰好对应fib(0)到fib(30)这31个不重复的n值,每个只被计算了一次。
跟朴素递归fib_plain(28)要29毫秒对比一下。
结果1000次缓存命中fib_cached(28)总耗时: 5.17e-05秒 → 单次约51.7纳秒 cache_clear()后第一次fib_cached(28)(冷缓存但仍是记忆化递归): 9.42e-06秒, 调用29次 fib_plain(28)完全不用缓存: 0.0292秒 加速比(naive/冷缓存首次): 约3102倍
lru_cache带来的核心收益不是"缓存命中快"(这只是锦上添花),而是它把递归的调用模式本身从指数级改造成了线性级——哪怕缓存是空的,只要函数体本身写成了"用已缓存的自身结果拼出下一个结果"这种递归形式,第一次计算也是线性时间,这跟命中率高不高是两件独立的事。
每一步都是本文上面用functools.lru_cache跑出来的原始结果。
functools.lru_cache / cache_info() / cache_clear():对比缓存命中、冷缓存首次调用和无缓存三种耗时