pytest-benchmark比手写time.time()更可靠,因其自动预热、多次运行取中位数、分离setup/teardown开销,并提供标准差等统计指标,避免因单次抖动、GC或CPU预热不足导致误判。

pytest-benchmark 为什么比手写 time.time() 更可靠
直接用 time.time() 或 time.perf_counter() 包裹函数测耗时,容易受单次执行抖动、GC 干扰、CPU 预热不足影响,结果波动大、不可复现。而 pytest-benchmark 默认自动预热(warmup)、多次运行取统计值(如中位数)、分离 setup/teardown 开销,并提供 stats 字段暴露标准差、R² 等质量指标——这些不是“锦上添花”,是避免得出“优化后变慢了”这类错误结论的前提。
它本质是 pytest 的 fixture 扩展,不侵入业务逻辑,也不要求你改测试结构,只需把原 def test_xxx(): 改成接收 benchmark fixture 即可。
安装与最简集成:三步跑通第一个 benchmark
别用 pip install pytest-benchmark 后就急着写测试——漏掉 pytest 配置或 fixture 命名错误会导致 fixture 'benchmark' not found。
- 确保项目已安装
pytest(≥6.0),再执行pip install pytest-benchmark - 新建文件
test_speed.py,内容如下:
def test_list_comprehension(benchmark):
result = benchmark([i * 2 for i in range(10000)])
assert len(result) == 10000
注意:不是 benchmark(lambda: [...]),也不是 benchmark.pedantic(...)——最简用法就是把待测表达式直接传给 benchmark() 调用。
立即学习“Python免费学习笔记(深入)”;
- 终端运行
pytest test_speed.py --benchmark-only(加--benchmark-only可跳过普通 test,只跑 benchmark)
区分 benchmark.group 和 benchmark.name:避免报告混淆
多个测试函数默认按函数名分组,但如果你有不同输入规模的同一算法(比如 test_sort_100、test_sort_1000),pytest-benchmark 会把它们当成独立 benchmark,无法横向对比。这时要用 group 统一归类,再用 name 标识差异点:
def test_sort_small(benchmark):
data = list(range(100))
benchmark.group = "sort"
benchmark.name = "100 items"
benchmark(sorted, data)
<p>def test_sort_large(benchmark):
data = list(range(1000))
benchmark.group = "sort"
benchmark.name = "1000 items"
benchmark(sorted, data)
运行时加 --benchmark-group-by=group,报告里就会按 group 分块,同组内 name 并排显示,方便看增长趋势。漏设 group 就只能靠人眼对齐函数名,极易看错行。
另外,benchmark.name 不能含空格或特殊字符(如 "100 items" 是合法的,但 "100-items!" 可能导致 HTML 报告渲染异常)。
setup/teardown 性能开销必须显式剥离
常见错误是把数据生成写在 benchmark 调用内部:benchmark([i for i in range(N)])——这会把列表构造时间也算进被测函数耗时。正确做法是用 setup 参数提前准备数据,只让 benchmark 测核心逻辑:
def test_json_loads(benchmark):
import json
payload = json.dumps({"a": list(range(1000))})
# ✅ 正确:payload 构造在 setup 中,不计入耗时
benchmark(
json.loads,
payload,
setup=lambda: (payload,) # 返回元组,作为被测函数参数
)
如果 setup 本身较重(比如要读文件、建 DB 连接),还可进一步拆成 setup + teardown 函数,避免重复初始化。忽略这点,测出来的“优化提升 20%”可能全是 setup 消除带来的假象。
复杂场景下,benchmark.pedantic() 更可控,但它强制要求你写明 setup、args、kwargs,稍繁琐;日常够用优先选简洁的 benchmark(func, *args, **kwargs) 形式。
真正难的不是跑出数字,而是确认那个数字到底在测什么——setup 写在哪一行,benchmark 包哪一段,决定了你是在优化算法,还是在优化数据准备。



















