Python 代码运行时间若用于日志,可以用 time.time;测量短代码应优先使用 perf_counter;做可重复的微基准测试则用 timeit。一次运行的数字通常不可靠,正确做法是预热、重复多次,并同时记录输入规模和运行环境。

你改写了一个循环,运行后快了 0.01 秒,于是认定新方案性能更好;第二次执行,结果却反过来了。计时不是在代码前后各放一行时间函数那么简单。本文基于 Python 3,比较 time、perf_counter 与 timeit 三种方式,并讲清预热、重复、异步函数和基准结果解读。今天这篇文章,编程狮帮你建立一套可以复现的 Python 代码运行时间测量方法。
一、测量 Python 代码运行时间前先定目标
计时目标大致分为三类。第一类是业务日志,例如一次文件处理用了多久;第二类是定位瓶颈,需要知道某个阶段的耗时;第三类是微基准,要比较两种很短的实现。目标不同,适合的计时器也不同。
墙上时钟时间表示现实世界经过了多久,但它可能受系统校时影响。单调时钟只保证数值持续向前,更适合计算时间间隔。高分辨率计时器则尽量提供更细的测量精度,适合短代码。
第一次接触 Python 标准库时,可以先浏览 Python3 基础教程。本文所有示例都使用 Python 3,并且只比较同一台机器、同一解释器进程中的相对结果。
⚠️ 注意:不同电脑上的绝对耗时不能直接比较。CPU、操作系统、后台负载、Python 版本和依赖版本都会影响数字。
二、time.time:适合记录业务开始与结束
time.time 返回自 Unix 纪元以来的秒数,适合日志时间戳和耗时较长的业务步骤。它的用法直观,但系统时间调整可能影响间隔,所以不应作为精密微基准的首选。
import time
def build_report():
values = [number * number for number in range(500_000)]
return sum(values)
start = time.time()
result = build_report()
elapsed = time.time() - start
print(f"w3cschool demo result={result}")
print(f"elapsed={elapsed:.6f}s")
这段代码适合回答“整个报告生成花了多少秒”。如果要比较几微秒或几毫秒的操作,系统调度和计时器精度会占据结果的较大比例,应换用 perf_counter 或 timeit。
三、perf_counter:测量一段代码的首选
time.perf_counter 是高分辨率单调计时器,专门用于测量时间间隔。它会把休眠时间也算进去,适合端到端测量函数、接口调用或代码区块。
from time import perf_counter
def normalize_names(names):
return [name.strip().lower() for name in names]
data = [" Python ", " JAVASCRIPT ", " SQL "] * 100_000
start = perf_counter()
result = normalize_names(data)
elapsed = perf_counter() - start
print(result[:3])
print(f"perf_counter={elapsed:.6f}s")
perf_counter 的起点没有业务含义,你只能用两次读数相减。它比 time.time 更适合 Python 代码运行时间测量,但单次结果仍然可能受垃圾回收、缓存、CPU 调频和其他进程影响。
需要为多个函数复用计时逻辑时,可以写一个上下文管理器:
from contextlib import contextmanager
from time import perf_counter
@contextmanager
def timer(label):
start = perf_counter()
try:
yield
finally:
elapsed = perf_counter() - start
print(f"{label}: {elapsed:.6f}s")
with timer("sort w3cschool numbers"):
sorted(range(300_000), reverse=True)
finally 能保证代码块即使抛出异常,也会输出已经经过的时间。常用语法需要查阅时,Python 速查手册 可以帮助你核对上下文管理器和异常处理写法。
四、timeit:比较短代码要重复测量
timeit 会重复执行目标语句,并尽量减少常见干扰。它适合比较短函数、表达式或数据结构操作,不适合直接测网络请求和数据库等外部 I/O。
from timeit import repeat
setup = "values = list(range(10_000))"
list_comp = repeat(
"[value * 2 for value in values]",
setup=setup,
repeat=5,
number=1_000,
)
map_call = repeat(
"list(map(lambda value: value * 2, values))",
setup=setup,
repeat=5,
number=1_000,
)
print("list comprehension best:", min(list_comp))
print("map best:", min(map_call))
repeat=5 表示做五组测量,number=1000 表示每组执行一千次。通常可以先看最小值,因为较大的结果更可能混入系统调度等外部干扰;同时也应观察各组差异,差异过大说明环境不稳定。
命令行也能调用 timeit:
python -m timeit -s "values=list(range(1000))" "sum(values)"
不要为了获得更漂亮的结果调整输入。基准数据应该与真实业务接近,并在报告里写明 Python 版本、操作系统、输入规模、重复次数和统计方式。
五、异步函数与 I/O 应该怎样计时
异步代码的端到端耗时仍可以用 perf_counter 包住 await。这个数字包含事件循环调度和等待 I/O 的时间,适合回答“用户等待了多久”,但不等于 CPU 真正执行了多久。
import asyncio
from time import perf_counter
async def fetch_lesson():
await asyncio.sleep(0.05)
return {"site": "w3cschool", "ok": True}
async def main():
start = perf_counter()
result = await fetch_lesson()
elapsed = perf_counter() - start
print(result, f"elapsed={elapsed:.6f}s")
asyncio.run(main())
如果要定位异步流程中的 CPU 瓶颈,单纯端到端计时不够,还要结合 profiler、事件循环监控和分段计时。网络与数据库还需要记录服务端延迟、重试次数和响应大小,不能把所有等待都归因于 Python 代码。
5.1 如何得到可信的基准结果
可信结果至少要做到六点:
- 固定 Python 与依赖版本;
- 使用接近真实业务的数据规模;
- 在正式记录前运行一到数次预热;
- 重复多组测量,而不是只跑一次;
- 同一轮测试交替执行候选方案,减少环境漂移;
- 同时检查输出正确,避免拿“少做了事情”的方案获胜。
| 方法 | 推荐用途 | 是否适合短代码 | 主要风险 |
|---|---|---|---|
| time.time | 日志、较长业务步骤 | 不推荐 | 可能受系统校时影响 |
| perf_counter | 函数与代码区块耗时 | 适合 | 单次结果有噪声 |
| timeit | 可重复微基准 | 最适合 | 容易脱离真实场景 |
优化前先确认耗时占比。某个函数快了一倍,如果它只占总请求的 1%,用户几乎感受不到变化。先用分段计时或性能分析器找到瓶颈,再用 timeit 比较局部实现,顺序不能反过来。

总结
Python 代码运行时间的测量方法取决于目标:业务日志用 time.time,代码区块优先用 perf_counter,短代码比较用 timeit。任何单次数字都只是样本,只有固定环境、使用真实输入、预热并重复,结果才有解释价值。
你需要带走三点:计时器要与问题匹配;正确性必须先于速度;局部变快不等于系统变快。下一步可以为项目最慢的三个流程加入分段计时,再挑真正占比高的函数做微基准。
延伸学习
想把计时与 Python 实战串起来,可以按这个顺序:
- 先跟着 Python 系统学习课程 巩固函数、模块和标准库基础;
- 再读 Python 列表方法性能比较,观察真实操作怎样设计对比;
- 最后使用 Python 代码格式化工具 整理自己的基准脚本,保证每轮测试代码一致。
常见问题
Q:perf_counter 和 process_time 有什么区别?
perf_counter 统计现实经过时间,包括等待和休眠;process_time 主要统计当前进程消耗的 CPU 时间,不包含休眠。测用户等待时间用前者,分析纯计算 CPU 成本时可补充后者。
Q:timeit 的 number 越大越好吗?
不是。number 要大到让计时明显高于计时器噪声,但不必让测试持续很久。先让 timeit 自动选择或逐步调整,再保持所有候选方案使用相同次数。
Q:为什么同一段代码每次计时不同?
操作系统调度、后台程序、缓存、垃圾回收和 CPU 频率都会造成波动。重复多组、记录分布,并在相同环境交替测试,可以减少误判。
Q:测量时需要关闭垃圾回收吗?
取决于目标。timeit 默认会暂时关闭垃圾回收以减少干扰,但如果业务大量创建循环对象,垃圾回收本身就是成本的一部分,应额外做一组开启垃圾回收的真实场景测试。

免费 AI IDE



