timeit更准是因为它禁用gc、用perf_counter、自动百万次循环并取最小值/中位数以压低噪声;手写perf_counter易受gc、调度等干扰。

timeit 为什么比 time.perf_counter() 单次套壳更准?
因为 timeit 不是“计个时”,而是跑一套受控实验:它默认禁用垃圾回收(gc.disable()),用 time.perf_counter() 作为底层计时器,自动执行百万次循环(默认 number=1000000),再取最小值或中位数——这些动作共同压低了系统调度、CPU 频率抖动、GC 突发、缓存预热等噪声。而手写 perf_counter() 套两行代码,哪怕加了循环,也拦不住 GC 在第 999 次时突然介入,或某次被后台进程抢占。
setup 参数漏写或错放,会导致 NameError 或结果失真
常见错误是直接传字符串语句却忘了注入依赖:
data = list(range(1000))timeit.timeit('sorted(data)', number=100000) → NameError: name 'data' is not defined
原因:timeit 在独立命名空间里执行 stmt,外部变量不可见。正确做法有二:
- 用
setup字符串把依赖一并声明:timeit.timeit('sorted(data)', setup='data = list(range(1000))', number=100000) - 或用
globals=locals()共享当前作用域:timeit.timeit('sorted(data)', globals=locals(), number=100000)(更直观,但注意别在循环里反复调用)
测函数调用不加括号,实际测的是函数对象创建开销
写成 timeit.timeit(my_func, number=100000) 是错的——这测的是“把函数对象赋值给局部变量”这个动作,不是执行它。
必须让 timeit 执行调用本身:
timeit.timeit(lambda: my_func(), number=100000)- 或
from functools import partial;timeit.timeit(partial(my_func), number=100000) - 若需传参:
timeit.timeit(lambda: my_func(arg1, arg2), number=100000)
注意:lambda 闭包捕获的是变量引用,如果 arg1 在外层循环中变化,得用默认参数固化,否则最后一轮会覆盖全部。
repeat() 返回列表,但只取 min() 容易被“假快”误导
timeit.repeat(repeat=5, number=100000) 返回 5 个总耗时数字。很多人习惯 min(result),但这可能对应某次触发了 JIT 缓存、常量折叠,或恰好没被 GC 打断——它不代表典型表现。
更稳妥的做法:
- 先看分布:有没有某次明显偏低(比如比中位数低 >30%)?若有,剔除再算
- 优先取
median(result),对异常值鲁棒 - 若目标是“最理想路径性能”,才用
min(),但要同步检查是否稳定(比如换不同number值重测)
真正容易被忽略的点是:setup 里的初始化逻辑,哪怕只执行一次,如果本身耗时(如加载大文件、建连接),也会悄悄拉低后续所有测量值——它不计入计时,但占用了真实资源。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











