Hypothesis Shrinker 基准测试实战:用 benchmark 目录对比 shrink 次数并生成图表
发布时间:2026/9/25 2:17:17来源:尧图网络
测试开发工具【免费下载链接】hypothesisThe property-based testing library for Python项目地址https://gitcode.com/gh_mirrors/hy/hypothesis点击查看免费下载Hypothesis 的benchmark/目录是一套专为shrinker收缩器性能对比而设计的基准测试工具链先用 pytest 插件在两个代码版本上分别收集minimal()调用时的收缩调用次数再用 Vega 规范渲染出对比图。本文以 benchmark/README.md 为主线结合仓库源码讲解从数据采集到出图的完整流程读完你可以自行复现新版本 vs 旧版本的 shrink 效率对比。benchmark 目录的定位数据采集与绘图的分离benchmark/README.md 明确说明了该目录的分工数据采集由测试目录下的conftest.py完成通过 pytest 插件机制而benchmark 目录本身只负责把结果画成图。目录下共三个文件文件职责benchmark/README.md使用说明与命令清单benchmark/graph.py绘图入口读取 JSON、计算统计量、调用 Vega 渲染benchmark/spec.jsonVega v5 图表规范双轴柱状图 置信区间 汇总面板这种采集与绘图分离的设计让同一份 benchmark 数据既可以对接不同图表模板也便于只重跑绘图而不重跑测试。运行完整基准测试的三步流程README 给出的流程是典型的同一套测试、两个版本、共享一个输出文件的对比方式在新版本上收集数据当前 checkout 的代码pytest tests/ -n auto --hypothesis-benchmark-shrinks new --hypothesis-benchmark-output data.json切回旧版本后再次收集git checkout到旧 commit使用同一个data.json路径pytest tests/ -n auto --hypothesis-benchmark-shrinks old --hypothesis-benchmark-output data.json使用同一个输出路径第二次运行会以追加方式写入最终data.json同时包含old与new两组结果。生成对比图python benchmark/graph.py data.json shrinking.png流程中的关键点-n auto启用 pytest-xdist 并行-k ...可附加在两条采集命令之后用于只跑与 shrink 相关的子集例如-k minimal and not slow从而缩短基准周期。两次运行之间必须切换代码版本否则old与new采集的是同一份数据图表将毫无意义。README 中after switching to the old version即指此操作。依赖与前提采集数据阶段无需额外依赖它是通过 Hypothesis 测试套件自带的 pytest 钩子实现的绘图阶段需要额外安装两个包pip install scipy vl-convert-pythonscipy在 graph.py 中用于计算 t 分布的置信区间临界值vl-convert-python把 Vega 规范直接转换为 PNGgraph.py 中调用vl_convert.vega_to_png(spec, ppi200)默认 72 ppi 偏模糊这里显式提高到 200 ppi。迭代次数默认固定为 5README 特别强调This hooks anyminimal()calls and reports the number of shrinks. Default (and currently unchangeable) number of iterations is 5 per test. 也就是说每个测试会被重复执行 5 次收缩从而对单次测量的随机波动做简单平均。这一设定在 conftest.py 中有源码佐证for _ in range(5 - 1): item.runtest()先跑 4 次最后一次通过yield交给 pytest 常规执行总计 5 次且当前没有命令行参数可以修改这个值。数据采集的底层机制pytest 钩子 Shrinker 打补丁采集逻辑全部位于 hypothesis/tests/conftest.py与 README 所述code for collecting the data is inconftest.py一一对应。命令行参数注册在pytest_addoption中注册了两个选项conftest.py--hypothesis-benchmark-shrinks取值只能是new或old--hypothesis-benchmark-outputJSON 输出文件路径。pytest_configure中做了一个联动校验conftest.py一旦指定了--hypothesis-benchmark-shrinks必须同时给出--hypothesis-benchmark-output否则直接断言失败。同时代码注释指出由于 pytest-xdist 的会话级 fixture 限制pytest-dev/pytest-xdist#271 相关当前实现需要锁文件或等价机制才能完全支持 xdist这里通过每个 worker 各写一个结果文件、主进程最后合并的方式绕开。只基准测试调用 minimal() 的测试在pytest_runtest_call钩子中一旦检测到 benchmark 模式就转入_benchmark_shrinksconftest.py。_benchmark_shrinks的第一步是廉价的源码过滤conftest.pyif minimal( not in inspect.getsource(item.function): pytest.skip((probably) does not call minimal())即凡是函数源码中没有出现minimal(字样的测试会被直接跳过。这样就能把基准聚焦到那些通过 tests/common/debug.py 中的minimal()辅助函数来度量最小反例质量的测试上避免把海量普通测试也拉进基准。从仓库统计看minimal(在tests/cover/、tests/conjecture/、tests/array_api/等多个测试模块中都有大量使用如 tests/cover/test_complex_numbers.py、tests/conjecture/test_engine.py 等因此覆盖面相当可观。通过 monkeypatch 计数收缩调用核心计数逻辑是给Shrinker.shrink打补丁conftest.pyactual_shrink Shrinker.shrink shrink_calls [] shrink_time [] def shrink(self, *args, **kwargs): start_t timer() result actual_shrink(self, *args, **kwargs) shrink_calls.append(self.engine.call_count - self.initial_calls) shrink_time.append(timer() - start_t) return result monkeypatch MonkeyPatch() monkeypatch.setattr(Shrinker, shrink, shrink)这里self.engine.call_count是引擎累计的测试函数调用次数engine.py 中self.call_count 1self.initial_calls是 Shrinker 启动时的基线shrinker.py。两者相减即收缩阶段额外执行的测试调用数也就是shrink 次数。这个度量直接反映收缩器的效率同样能缩到最小反例消耗的调用越少越好。计时用time.process_timeconftest.py度量的是进程 CPU 时间而非墙钟时间受系统负载影响更小。按 xdist worker 分文件写出主进程最后合并每个 xdist worker 把结果写到输出路径同目录下的shrinking_results_worker.jsonconftest.py避免多进程并发写同一文件测试收尾时pytest_sessionfinish只在 controller 进程执行合并conftest.py读取所有shrinking_results_*文件、按 nodeid 合并calls与time字典随后删除临时文件最终把{mode: results}mode 为new或old合并进用户指定的data.json文件不存在则新建已存在则按 key 追加这正是第二次运行 append 数据的机制。最终data.json的结构为{ old: {calls: {node_id: [n1, n2, ...]}, time: {node_id: [...]}}, new: {calls: {node_id: [n1, n2, ...]}, time: {node_id: [...]}} }node_id是去掉hypothesis/tests/前缀后的测试节点名conftest.py每个值都是 5 次迭代的调用数/耗时数组。绘图graph.py 的数据处理管线graph.py 用click定义了一个简单的命令行入口plot(data, out)接收 JSON 数据路径和输出图片路径。数据校验与逐节点对比_process_benchmark_datagraph.py先做严格校验assert set(data) {old, new} ... assert set(old_calls) set(new_calls)要求数据中恰好有old/new两组且两组覆盖的测试节点完全一致否则说明两次采集的测试集合不匹配直接断言失败。随后逐节点计算三个统计量并写入图表数据字段含义absoluteold - new的平均值收缩调用数的绝对差值越大越好absolute_ci_lower/upper差值均值的 95% 置信区间边界nx归一化后的倍数变化见下nx的计算逻辑在_diff_timesgraph.py中比较特别当old 0 and new 0时返回 0old 0时也返回 0注释明说与其给 inf 不如给 0 更合理否则v (old - new) / old若0 v 1再映射为(1 / (1 - v)) - 1把减少百分比变换成新是旧的 N 倍这种倍数语义方便与n× change轴对应。置信区间假设等方差的两样本 t 检验_mean_difference_cigraph.py实现的是假设两总体方差相等的合并标准差 t 检验pooled_std sqrt(((n1-1)*var1 (n2-1)*var2) / df) se pooled_std * sqrt(1/len(n1) 1/len(n2)) t_crit stats.t.ppf((1 confidence) / 2, df) return t_crit * se代码注释坦诚说明这是近似This assumes equal variances between the populations of n1 and n2. This is not necessarily true (new might be more consistent than old), but its good enough.——新版本可能比旧版本更稳定方差更小等方差假设未必成立但对基准目的而言足够。注意absolute的 CI 上下界直接用同一个ci_shrink值对称区间而nx的 CI 上下界目前固定为 0graph.py即未绘制倍数变化的误差线。Vega 渲染与输出plot_vegagraph.py把基准数据注入 spec.jsonspec json.loads(vega_spec.read_text()) spec[data].insert(0, {name: source, values: data}) for key, value in parameters.items(): spec[signals].append({name: key, value: value}) f.write(vl_convert.vega_to_png(spec, ppi200))plot()传入的三个信号参数graph.pytitle图表标题固定为Shrinking benchmark (calls)sum_old/sum_new新旧两版各自的收缩调用数总和absolute_axis_title左轴标题即shrink call change (old - new, larger is good)明确提示正值代表新版本更好。图表规范解读双轴对比 汇总面板spec.json 是一份标准的 Vega v5 规范值得关注的结构有双轴设计左轴蓝色#4285F4绘制absolute即收缩调用绝对差标题取自信号absolute_axis_title右轴红色#DB4437绘制nx即倍数变化标题n× change。两种尺度的 domain 分别由shrink_domain与nx_domain两个聚合数据源计算并纳入 CI 边界以保证误差线不被截断。每个节点的展示元素marks 定义在 spec.json一条带半透明 error bar 的 rule两个 rect 标记 CI 上下端点一大一小两个 symbol 叠加出带光晕的点小 symbolsize 38、opacity 0.85保证点在浅色大圆size 100、opacity 0.35之上清晰可见两组 0 值参考线rule分别对应两轴。汇总面板spec.json右上角一个圆角灰底 group展示Mean/Sum来自shrink_stats聚合分别对absolute和nx求 mean 与 sum、sum(old)、sum(new)以及old / new比值——这是最直观的整体效果数字比值越大说明新版本 shrink 总开销越低。基准数据通过source数据源注入示例数据节点test_node_1…test_node_5仅供规范调试真实运行时会被实际数据替换。完整工作流与注意事项总结一次典型的 shrinker 对比基准可以概括为# 1. 安装绘图依赖采集阶段无需 pip install scipy vl-convert-python # 2. 新版本采集 pytest tests/ -n auto --hypothesis-benchmark-shrinks new --hypothesis-benchmark-output data.json # 3. 切换到旧版本git checkout old-commit后采集写入同一文件 pytest tests/ -n auto --hypothesis-benchmark-shrinks old --hypothesis-benchmark-output data.json # 4. 绘图 python benchmark/graph.py data.json shrinking.png需要留意的事实与限制每个测试固定跑5 次收缩当前不可配置见 conftest.py两次采集必须使用相同的测试集合否则graph.py的set(old_calls) set(new_calls)断言会失败只基准测试函数源码含minimal(的用例其它测试会被跳过绘图依赖scipy与vl-convert-python且输出 PNG 以 200 ppi 渲染以保证清晰度data.json采用追加式写入先跑new再跑old或反之均可graph.py只要求两组 key 都存在若需只跑部分用例可在两条采集命令后追加-k ...过滤但两边的过滤条件必须一致。这套工具链把shrink 次数这一关键性能指标做成了可重复、可量化的对比实验是评估 Hypothesis 收缩器改动例如 shrinker.py 中新增 shrink pass是否真正带来改进的实用手段。赞分享测试开发工具【免费下载链接】hypothesisThe property-based testing library for Python项目地址https://gitcode.com/gh_mirrors/hy/hypothesis点击查看免费下载相关推荐Bluebird 基准测试全解析基于 benchmark 目录复现 Promise 性能对比实验Bluebird 基准测试全解析基于 benchmark 目录复现 Promise 性能对比实验 导读 本文围绕 Bluebird 仓库自带的 benchma后端AIBrix 回归基准测试实战复现 PS Benchmark 并对比分布式 KV Cache 路由性能AIBrix 回归基准测试实战复现 PS Benchmark 并对比分布式 KV Cache 路由性能 本篇技术指南基于仓库 test/regression/云原生大模型模型推理服务API网关LLM 网关弹性伸缩可观测性后端Apache Arrow 基准测试实战使用 Archery 运行 Benchmark、对比结果与回归检测Apache Arrow 基准测试实战使用 Archery 运行 Benchmark、对比结果与回归检测 本文是一篇面向 Apache Arrow 开发者的基数据工程大数据序列化数据分析上一篇Docs as Code实践指南基于Write the Docs平台的最佳工作流下一篇终极免费数学自学指南如何从零基础到数学专业水平 创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网