Python3 timeit测试lru_cache装饰斐波那契函数异常问题
异常现象的核心原因
两个异常现象和timeit模块本身无关,完全是测试逻辑与lru_cache缓存特性冲突导致的:
- 没有触发递归深度报错的原因:你从z=0开始从小到大依次调用
fast_fib(z),每次计算fast_fib(z)时,fast_fib(z-1)和fast_fib(z-2)的结果已经在前序循环中被计算过、存入lru_cache了。此时调用fast_fib(z)根本不会触发深层递归——进入函数后两次递归调用直接命中缓存返回,整个调用栈深度始终只有1层,自然碰不到默认1000层左右的递归深度限制。你可以自行验证:清空缓存后直接调用fast_fib(1000)必然触发递归报错;但如果先依次跑完fast_fib(0)到fast_fib(999),再调用fast_fib(1000),不仅不会报错,返回速度还极快,和你在timeit里看到的现象完全一致。 - 耗时始终极小的原因:和上述逻辑一致,当所有前置结果都在缓存中时,第一次执行
fast_fib(z)仅需做一次加法、把结果存入缓存,后续99次timeit触发的调用直接命中缓存返回值,连函数内部逻辑都不会执行,本质测的是字典查表的耗时,本来就在百纳秒量级,和斐波那契递归计算的耗时没有任何关系。
你脱离timeit直接测试会触发递归错误,是因为直接传入大n值调用时缓存是空的,函数需要从n一路递归压栈到0/1基准值,栈深超过阈值自然报错,这才是无缓存场景下的真实表现。
测试写法的错误
你的测试存在三个明显问题:
- 测试顺序完全违背测试目标:从n=0递增调用带缓存的函数,相当于主动给缓存做了全量预热,完全没有触发真实的递归计算路径,测出来的结果没有参考价值。
- 没有区分测试场景:带缓存的函数存在「冷启动(无缓存首次计算)」和「热启动(缓存命中直接返回)」两种完全不同的性能表现,你把两种场景混在同一个测试循环里,得到的结果是失真的。
- timeit参数用法存在冗余:在循环内传
globals=locals()虽然能拿到当前的z值,但更稳妥的写法是直接传入lambda闭包,避免作用域查找带来的额外干扰。
合理的基准测试方案
根据测试目标的不同,测试方案要做对应调整:
测试无缓存冷启动计算性能
这也是你原本想测的递归计算性能,核心要求是每次测试前清空缓存,避免前序结果污染:
import sys from functools import lru_cache from timeit import timeit # 按需调整递归深度,比如测试n=2000就把递归上限设到2000以上的安全值 sys.setrecursionlimit(10000) @lru_cache def fast_fib(n): if n in [0, 1]: return n return fast_fib(n-1) + fast_fib(n-2) # 选择要测试的n值,不要从小到大连续测试 test_cases = [10, 50, 100, 500, 1000, 2000] for n in test_cases: # 每次测试前必须清空缓存 fast_fib.cache_clear() # 冷启动测试number设为1即可,多次运行后续调用都会命中缓存 cost = timeit(lambda: fast_fib(n), number=1) print(f"fib({n}) 冷启动计算耗时: {cost:.6f}s")
这种写法下如果不清缓存、直接调用超过默认递归深度的n值,会正常抛出递归深度超限错误,和你直接在timeit外运行的表现完全一致。
测试缓存命中后的热调用性能
如果你要测缓存生效后的调用开销,不需要清空缓存,提前预热后测试即可,你之前测到的2e-7秒量级就是这个场景下的正常结果,本质是Python函数调用+字典查表的开销。
内容的提问来源于stack exchange,提问作者foderking
相关产品推荐
相关产品推荐

