Python 统计代码运行时间:附 3 种方法新手避坑一次讲清

编程狮(w3cschool.cn) 2026-09-22 15:36:58 浏览数 (68)
反馈

要统计 Python 代码运行时间,最稳妥的起点是用 time.perf_counter() 取前后时间戳相减,因为它不受系统时钟回拨影响、精度最高。很多新手习惯用 time.time() 或只跑一次就下结论,结果得到的数字忽高忽低、完全不可信。

统计代码运行时间3 种方法对比

本文依次给出三种可落地的方法:第一是用 perf_counter 手动掐表,适合单段脚本;第二是用标准库 timeit 做多次平均,适合对比两种写法的快慢;第三是写一个计时装饰器,套在任意函数上自动统计。每种方法都配可运行示例,并穿插最常见的坑——比如把文件读写、网络请求这类 I/O 也算进「算法耗时」,或者对一次性代码只测一遍就信以为真。读完你就能给自己的 Python 程序装上靠谱的秒表,调优时不再靠猜。

一、先看结论:三种计时方法怎么选

方法 精度 是否多次平均 适用场景 不适合场景
time.perf_counter() 否,需手动多跑 单段脚本粗略计时 需要统计分布
timeit 是,自动多轮 比较两种写法快慢 含 I/O、随机种子
计时装饰器 否,需手动多跑 多个函数统一统计 极短代码批量对比

一句话:粗略估时用 perf_counter,比较写法用 timeit,多函数统计用装饰器。

二、先确认 Python 环境与计时精度

动手计时前,先确认本机 Python 版本和运行的解释器,避免把不同版本的性能差异误当成代码优劣。可以用命令行直接查看,或在脚本里打印,两种方式都建议养成习惯:

# 命令行查看 Python 版本
python --version

import sys

# 打印当前解释器版本
print(sys.version)

不同小版本在循环、字典等操作上可能有肉眼可见的差距,所以对比实验务必在同一环境跑。

计时这件事本身也有精度之分:

计时函数 类型 是否受系统校时影响 适合场景
time.time() 墙上时钟 是,可能回拨 记录时间戳
time.perf_counter() 高精度计数器 否,只增不减 测短代码耗时
time.monotonic() 单调时钟 测超时、间隔
time.process_time() CPU 时间 测纯 CPU 计算

老式的 time.time() 返回的是墙上时钟,可能被 NTP 校时或系统休眠打断;而 time.perf_counter() 基于系统最高精度计数器,只增不减,最适合测短代码。

如果还不熟悉怎么查看解释器信息,可以看这篇 Python 版本笔记,里面把命令行和脚本两种方式都列全了,照着做就不会踩环境不一致的坑。确认环境一致后,我们再进入第一种具体写法。

三、用 time.perf_counter 手动掐表

最直观的计时方式就是「开始前记一下,结束后再记一下,两数相减」。perf_counter 返回的是浮点秒数,差值就是这段代码的耗时。

下面统计一百万次求和,麻雀虽小但覆盖了完整套路:

import time

# 开始计时
start = time.perf_counter()

# 待测代码:一百万次求和
total = 0
for i in range(1_000_000):
    total += i

# 结束计时
end = time.perf_counter()

# 输出耗时
print("耗时:", end - start, "秒")

这种写法门槛低、看得见过程,适合给整段脚本或某个函数体掐表。

需要注意:

注意事项 原因
计时区间只放待测代码 避免把打印、日志算进去
不要只跑一次 单次结果受 CPU 调度影响
连跑几次取中间值 降低偶然抖动
不要在循环内重复取时间 计时开销会污染结果

startend 之间只放你想测的代码,别顺手把打印、日志也包进去,否则测出来的是「计算加输出」的总时间,结论会被严重污染。

单次结果受 CPU 调度、后台进程影响会有抖动,所以手动掐表最好连跑几次取中间值,而不是拿第一次的数字当结论。当你只想粗略估个大概,这是最省事的方法,也最容易嵌入现有脚本。

四、用 timeit 做多次平均

标准库 timeit 的价值在于它自动把同一段代码跑很多遍再取平均,从源头压低偶然抖动,特别适合比较两种写法谁更快。

最简单的是命令行用法,几秒就能出结论:

# -n 1000 表示每次重复 1000 次
# -r 5 表示跑 5 轮
python -m timeit -n 1000 -r 5 "sum(range(1000))"

-n 是每次重复次数,-r 是轮数,最终给出最优一轮的平均耗时。

在代码里调用也很方便,适合写进 benchmark 脚本长期保留:

import timeit

# 统计 sum(range(1000)) 执行 1000 次的总耗时
t = timeit.timeit(
    "sum(range(1000))",
    number=1000
)

# 除以次数得到平均每次耗时
print("平均每次:", t / 1000, "秒")

需要注意 timeit 的边界:

边界 说明 建议
默认禁用 GC 纯计算时间偏理想化 涉及大量内存分配时手动开 GC
不适合 I/O 每次结果差异巨大 I/O 单独计时
不适合随机种子 结果不稳定 固定随机种子后再测
回答的问题 「哪种写法更快」 不是「业务跑一次多久」

timeit 默认会禁用垃圾回收,得到的纯计算时间偏理想化;若你的代码涉及大量内存分配,可手动打开 gc 更贴近真实。timeit 不适合包裹含 I/O 或随机种子的代码,否则每次结果差异巨大。

它回答的是「哪种写法更快」,而不是「这段业务跑一次要多久」,这个边界一定要分清。

五、写计时装饰器复用

当同一个项目里要统计很多函数,反复写 start/end 太啰嗦。把计时逻辑封装成装饰器,之后只要在目标函数头顶加一行 @timer 就能自动统计,干净又不易漏。

下面是一个最小实现,可直接放进工具模块反复使用:

import time
import functools

def timer(func):
    # functools.wraps 保留原函数的名称和文档
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # 开始计时
        start = time.perf_counter()

        # 执行原函数
        result = func(*args, **kwargs)

        # 结束计时
        end = time.perf_counter()

        # 输出函数名和耗时
        print(f"{func.__name__} 耗时 {end - start:.6f} 秒")

        # 返回原函数结果
        return result
    return wrapper

@timer
def heavy():
    # 待测函数:一百万次求和
    return sum(range(1_000_000))

heavy()

装饰器的好处是零侵入:业务代码一行不用改,计时逻辑集中维护。你可以进一步把耗时写进日志、累加统计,或在开发环境才开启。

装饰器增强 做法
写日志 用 logging 替代 print
可开关 用环境变量控制是否计时
累加统计 把耗时存入全局字典
开发环境才开启 用 if DEBUG 判断

对于需要横向对比多个函数性能的场景,装饰器比散落的 perf_counter 更可控,也更容易在提交前统一关掉,避免把调试输出带进生产。它把「计时」变成了一种可开关的能力,而不是四处粘贴的样板代码。

六、输出格式化与新手常见坑

拿到耗时数字后,别直接 print 一长串浮点,既不美观也不利于对比。通常保留两三位小数足够说明问题,太长反而干扰判断:

import time

# 开始计时
start = time.perf_counter()

# 待测代码
sum(range(1_000_000))

# 计算耗时
cost = time.perf_counter() - start

# 保留三位小数输出
print(f"耗时 {cost:.3f} 秒")

关于小数位数怎么取、四舍五入怎么处理,可以对照 Python 保留两位小数笔记 里的写法,避免自己手写格式化时踩精度坑。

新手最容易踩的坑:

常见坑 后果 正确做法
把 I/O 算进算法耗时 测到的是磁盘/网络速度 纯计算与 I/O 分开计时
只跑一次就下结论 单次抖动大,结果不可信 多次平均或取中间值
用 time.time() 测极短代码 精度不够,差值可能为 0 改用 perf_counter
计时区间包含打印/日志 测到的是「计算+输出」 区间只包待测代码
在循环内重复取时间 计时开销污染结果 循环外取一次

一是把 I/O 算进算法耗时,比如顺手把 open 读文件、网络请求包进计时区间,得到的其实是磁盘或网络速度,不是代码快慢;二是只跑一次就下结论,单次结果受系统抖动影响极大,务必多次平均。还有人用 time.time() 测极短代码,精度不够导致差值恒为 0,这种数字看了只会误导优化方向。

Python 三种计时方法对照

实际计时时,先在脚本里配置好计时区间只包待测代码,运行几次对比不同写法的耗时并检查输出是否符合预期;如果单次计时失败或抖动过大,改用 timeit 多次平均来修复精度问题,再验证结果是否稳定。记住短代码务必用高精度计数器,别用 time.time 测极短逻辑。示例代码按官方文档整理,可直接复用。

总结

统计 Python 运行时间,按需求选方法:

  • 想粗略估个大概用 time.perf_counter() 手动掐表;
  • 要比两种写法快慢用 timeit 多次平均;
  • 要横向统计多个函数就写计时装饰器。

无论哪种,请记住三条铁律:

  1. 计时区间只放待测代码;
  2. 短代码用高精度计数器;
  3. 结果必须多次平均。

把 I/O 和网络排除在算法计时之外,数字才有比较意义。把这些方法收进你的工具箱,编程狮上的 Python 实战练习里也能直接套用,调优时就不必再靠猜,而是用数据说话。

延伸学习

  1. Python 就业实战课 系统补齐 Python
  2. Python 运行时间检测笔记 看运行时间与内存检测
  3. Python3 教程 复习基础语法

常见问题

Q:为什么我测出来的耗时每次都不一样?

A:单次测量受 CPU 调度、后台进程和缓存影响会抖动。请用 timeit 多次平均,或手动连跑几次取中间值,避免拿第一次结果下结论。

Q:time.time 和 perf_counter 该用哪个?

A:测代码耗时优先 perf_counter,它精度高且不受系统校时影响;time.time 是墙上时钟,可能因 NTP 或休眠回拨,不适合短代码计时。

Q:含文件读写的代码怎么计时才准?

A:把 I/O 与纯计算分开计时,否则测到的是磁盘或网络速度。先单独测算法部分,再按需对 I/O 单独统计,两者不要混在同一个区间。

Q:timeit 默认禁用 GC 会影响结果吗?

A:会影响。timeit 默认禁用垃圾回收,得到的是偏理想化的纯计算时间。如果代码涉及大量内存分配,可以手动打开 GC 后再测,更贴近真实运行情况。

0 人点赞