Python timeit 模块的使用技巧

7次阅读

单次测量用 timeit.timeit(),但易受系统负载干扰;评估稳定性或排除异常值必须用 timeit.repeat(),它默认执行3组、每组1000000次,返回列表可取min()最可靠。

Python timeit 模块的使用技巧

timeit.timeit() 和 timeit.repeat() 怎么选

单次测量用 timeit.timeit(),但结果容易受瞬时系统负载干扰;想评估稳定性或排除异常值,必须用 timeit.repeat() —— 它默认执行 3 组,每组调用 number 次(默认 1000000),返回列表,你取 min() 最可靠。

常见错误是直接信 timeit.timeit() 的一次结果,尤其在笔记本或虚拟机上,CPU 频率波动、后台进程都可能让误差超 20%。

  • repeat=5, number=100000 比默认更稳妥,适合中等耗时代码
  • 如果函数执行很快(number 到 10⁶ 或更高,避免计时器本身开销占比过大
  • repeat() 后别取平均值,取 min() —— 这最接近“纯函数执行时间”,避开上下文干扰

setup 参数写错会导致 benchmark 失效

setup 不只是导入语句,它决定测试环境是否干净。漏掉变量预定义、错用字符串拼接、忘记 import,都会让 timeitNameError 或测到无关开销。

例如测 list.append(),如果在 stmt 里每次新建 list:stmt='l = []; l.append(1)',实际测的是创建 list + append,不是 append 本身。

立即学习Python免费学习笔记(深入)”;

  • 正确做法:在 setup 中初始化数据,stmt 只写待测操作,如 setup='l = []'stmt='l.append(1)'
  • 多行 setup?用三引号或 ; 连接,但注意换行符不被支持,setup='import math; x = 123' 是安全的
  • 避免在 setup 中执行耗时操作(如读文件、生成大列表),否则会污染计时结果

命令行调用 timeit 时路径和作用域容易出错

python -m timeit 最方便,但要注意当前工作目录和 Python 模块搜索路径 —— 它不自动包含当前目录,-m 模式下也无法直接 import 本地 .py 文件。

典型报错:ModuleNotFoundError: No module named 'myutils',哪怕 myutils.py 就在当前文件夹。

  • 解决方法一:加 -p .(Python 3.11+)或临时改 PYTHONPATH=.
  • 解决方法二:把待测代码写成字符串传入,用 -s 写 setup,如 python -m timeit -s "from math import sqrt" "sqrt(144)"
  • 别在命令行里用相对路径导入模块;也别依赖 __file__os.getcwd() —— timeit 的执行上下文不保证与你预期一致

timeit.Timer 实例比函数调用更灵活,但要手动管理命名空间

当你需要反复测多个 stmt、共享同一 setup,或想控制 globals/locals 时,timeit.Timer 是唯一选择。但它不会自动帮你隔离变量,locals 冲突或 globals 覆盖很常见。

比如你在 globals() 里传了 {'x': 10},又在 stmt 里写 x += 1,就会报 UnboundLocalError —— 因为 Python 认为 x 是局部变量,但首次引用前未赋值。

  • 安全做法:用独立字典传 globals,且确保所有变量都在里面初始化,如 g = {'x': 10}; t = timeit.Timer('x += 1', globals=g)
  • 避免混用 globalslocals;优先只用 globalslocals 在闭包或动态作用域下行为难预测
  • 如果 stmt 依赖类或函数定义,全写进 setup 字符串里,别指望从外部 globals 自动带入

真正影响结果的,往往不是算法本身,而是 setup 是否复位、number 是否足够大、以及你有没有在 min 结果里忽略掉那一次 GC 触发的抖动。

text=ZqhQzanResources