timeit.timeit()最适合测单个函数调用的精确耗时,它自动禁用GC、多次运行取最小值以排除系统抖动;避免用time.time(),因其受调度、IO、GC影响大,尤其在Python 3.11下偏差更明显。

用 timeit 测单个函数调用的精确耗时
timeit 是 Python 内置最可靠的微基准测试工具,它自动禁用 GC、重复多次执行、取最佳值,避免单次测量被系统抖动干扰。新手常直接用 time.time(),但那测的是 wall-clock time,受 I/O、调度、GC 影响极大,尤其在 Python 3.11 的新 GC 策略下偏差更明显。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 用
timeit.timeit()而非timeit.repeat()入门——后者适合做统计分析,但新手容易误读中位数/平均值 - 把待测函数和依赖数据一起写进
setup字符串,或用globals参数传入,避免变量查找开销污染结果 - 别测带 I/O 或随机行为的函数(比如读文件、调 API),
timeit的“纯计算”假设会崩 - 示例:测
sum(range(1000)),推荐写法是timeit.timeit("sum(range(1000))", number=1000000),不是timeit.timeit(sum(range(1000)), ...)——后者会先执行再传结果,完全测错了
用 cProfile 定位慢在哪一行、哪个子函数
当整个函数跑得慢,但不知道瓶颈在哪儿时,timeit 就不够用了。cProfile 是 C 实现的行级分析器,在 Python 3.11 中对协程和新字节码(如 CALL 指令优化)支持更稳,比旧版 profile 快 5–10 倍且误差更小。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 别用
python -m cProfile script.py测脚本全局——你会被 import 开销和顶层代码淹没,专注测目标函数:用cProfile.run('my_func(42)', sort='cumtime') - 关注
cumtime列(累计时间),而非tottime(纯函数内耗时),才能看出是自己函数慢,还是它调的json.loads或re.search拖累的 - 输出太长?加
p = pstats.Stats('profile.out'); p.sort_stats('cumtime').print_stats(10)只看前 10 行 - 注意:
cProfile本身有约 5%–10% 运行时开销,不能代替timeit做微秒级精度对比
Python 3.11 新特性:用 perf 工具链抓底层热点
Python 3.11 启用了自适应专用指令(如 LOAD_FAST_LOAD_FAST),但这些优化是否生效、是否被你的数据分布触发,cProfile 看不到。这时得靠 Linux 的 perf + py-spy 或原生 perf 支持(需编译时启用 --enable-profiling)。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 确认你的 Python 是用
./configure --enable-profiling编译的(Ubuntu/Debian 的python3.11-dev包默认开启;macOS Homebrew 默认关,得重装) - 运行
perf record -e cycles,instructions,cache-misses -g python3.11 my_script.py
,再用perf report --no-children查看哪些字节码指令或 C 函数占 CPU 周期最多 - 常见信号:
PyObject_GetAttr高频出现 → 属性访问太碎,考虑用__slots__或缓存;PyEval_EvalFrameDefault占比超 70% → 真正的 Python 层瓶颈,不是 C 扩展拖慢 - Windows 用户绕过:用
py-spy record -p <pid> -o profile.svg</pid>,它不依赖内核 perf,但采样精度略低
别踩这些坑:计时结果不可比的典型场景
很多“优化”后反而变慢,是因为计时方式本身引入了偏差。Python 3.11 的 JIT 尝试(虽未正式启用)和更快的启动路径,让冷启动/热启动差异比以前更敏感。
必须避开的操作:
- 在 Jupyter 中用
%timeit测含import的函数——每次运行都可能重新导入,而 import 在 3.11 有模块缓存预热逻辑,首次耗时高但后续骤降 - 用不同
number值比较两个timeit结果——比如一个设number=10000,另一个设number=100000,得出“快 10 倍”的假结论 - 测递归函数时不控制深度,导致栈溢出或 GC 触发时机错乱;Python 3.11 的 GC 对深层嵌套对象回收策略变了,影响显著
- 忽略输入数据特征:同一函数,测
[1]*1000和测list(range(1000))可能差 3 倍——因为 3.11 对小整数缓存和 list 预分配做了调整
真实优化永远从“明确问题场景+稳定可复现的输入+匹配生产环境的计时方式”开始,而不是一上来就改算法。



















