You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 00:33:35