Python 如何准确统计代码运行时间?time、perf_counter 与 timeit 对比

编程狮(w3cschool.cn) 2026-09-08 14:52:14 浏览数 (20)
反馈

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

Python 代码运行时间三种测量方法封面图

你改写了一个循环,运行后快了 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 如何得到可信的基准结果

可信结果至少要做到六点:

  1. 固定 Python 与依赖版本;
  2. 使用接近真实业务的数据规模;
  3. 在正式记录前运行一到数次预热;
  4. 重复多组测量,而不是只跑一次;
  5. 同一轮测试交替执行候选方案,减少环境漂移;
  6. 同时检查输出正确,避免拿“少做了事情”的方案获胜。
方法 推荐用途 是否适合短代码 主要风险
time.time 日志、较长业务步骤 不推荐 可能受系统校时影响
perf_counter 函数与代码区块耗时 适合 单次结果有噪声
timeit 可重复微基准 最适合 容易脱离真实场景

优化前先确认耗时占比。某个函数快了一倍,如果它只占总请求的 1%,用户几乎感受不到变化。先用分段计时或性能分析器找到瓶颈,再用 timeit 比较局部实现,顺序不能反过来。

Python 代码计时方法选择决策树

总结

Python 代码运行时间的测量方法取决于目标:业务日志用 time.time,代码区块优先用 perf_counter,短代码比较用 timeit。任何单次数字都只是样本,只有固定环境、使用真实输入、预热并重复,结果才有解释价值。

你需要带走三点:计时器要与问题匹配;正确性必须先于速度;局部变快不等于系统变快。下一步可以为项目最慢的三个流程加入分段计时,再挑真正占比高的函数做微基准。

延伸学习

想把计时与 Python 实战串起来,可以按这个顺序:

  1. 先跟着 Python 系统学习课程 巩固函数、模块和标准库基础;
  2. 再读 Python 列表方法性能比较,观察真实操作怎样设计对比;
  3. 最后使用 Python 代码格式化工具 整理自己的基准脚本,保证每轮测试代码一致。

常见问题

Q:perf_counter 和 process_time 有什么区别?

perf_counter 统计现实经过时间,包括等待和休眠;process_time 主要统计当前进程消耗的 CPU 时间,不包含休眠。测用户等待时间用前者,分析纯计算 CPU 成本时可补充后者。

Q:timeit 的 number 越大越好吗?

不是。number 要大到让计时明显高于计时器噪声,但不必让测试持续很久。先让 timeit 自动选择或逐步调整,再保持所有候选方案使用相同次数。

Q:为什么同一段代码每次计时不同?

操作系统调度、后台程序、缓存、垃圾回收和 CPU 频率都会造成波动。重复多组、记录分布,并在相同环境交替测试,可以减少误判。

Q:测量时需要关闭垃圾回收吗?

取决于目标。timeit 默认会暂时关闭垃圾回收以减少干扰,但如果业务大量创建循环对象,垃圾回收本身就是成本的一部分,应额外做一组开启垃圾回收的真实场景测试。

0 人点赞