使用 asv 进行基准测试

PyBroker 使用 asv(Airspeed Velocity) 跨提交跟踪回测性能。asv 运行基准测试,将每次提交的结果存储在 .asv/results/ 下,并生成用于可视化回归跟踪的 HTML 仪表盘。

基准测试套件位于 benchmarks/ 目录下,并通过 .github/workflows/asv-pr.yml 在每个 pull request 的 CI 中执行,该工作流会针对每个受支持的 Python 版本运行一次 asv continuous origin/<base> HEAD,并为每个版本发布一条包含差异的置顶 PR 评论。这些版本来自 .github/python-versions.json——测试矩阵和夜间基准测试运行同样以它为唯一真实来源。

安装

pip install asv
asv machine --yes          # one-time per machine

运行

对当前工作树进行基准测试:

asv run --quick            # one sample per benchmark, fastest feedback
asv run                    # calibrated samples, publication quality

比较两次提交:

asv continuous dev HEAD      # local equivalent of the CI gate
asv compare dev HEAD         # diff table

PR 门禁使用两个阈值:在 --factor 1.25 处拦截,并报告所有变动幅度达到 1.1 或以上的项,其中基准提交解析为 origin/<base branch>

asv continuous origin/dev HEAD --factor 1.25 --interleave-rounds
asv compare origin/dev HEAD --factor 1.1 --only-changed

第二条命令会重新读取第一条命令存储的结果,因此不会产生额外的基准测试开销。

为什么门禁的阈值比报告阈值更宽松:1.1 低于共享 runner 的噪声下限。在 src/benchmarks/ 在基准提交与目标提交之间逐字节相同的六次 asv continuous 运行中,仍有一次运行标记出了回归——且总是发生在耗时低于 2ms 的微基准测试上,比值最高达 1.18。而向前分析宏基准测试(耗时 100ms 及以上)从未出现变动。将门禁设在 1.25 可以过滤掉已测得的噪声;1.1 的表格则保留了较小的变动,供人工判断。

有两个标志能起到部分作用,但单独使用都不够。--interleave-rounds 会在两次提交之间交替运行轮次,而不是把每次提交的轮次集中运行,这样任务过程中的漂移(散热降频、噪声邻居、页缓存)会均匀影响两侧,而不会完全落在后运行的那次提交上;它复用现有的轮次,因此不会产生额外开销。--no-stats 则被刻意 使用:它会禁用显著性检验,仅将原始中位数与 --factor 进行比较。

如果噪声下限上升,应提高采样量(--attribute rounds=N),而不是放宽门禁的比例因子——放宽该因子会以牺牲真实的覆盖率为代价。

生成并预览 HTML 仪表盘:

asv publish
asv preview                # serves at http://127.0.0.1:8080

基准测试套件

asv 套件位于 benchmarks/ 目录下,分布在四个模块中。对于超过 1.25 倍的性能回退,CI 会使 PR 失败,除非该 PR 带有 bench-override 标签;对于超过 1.1 倍但未达到该标准的回退,则仅报告而不阻断。新增的热路径应当添加相应的基准测试。

  • bench_backtest.py —— 端到端的向前分析基准(预热、冷启动、扩展规模、模型、时间区间、无滑点),以及针对指标和评估内核、SymbolArrayStore、滞后特征准备和缓存的微基准测试。同时还会跟踪向前分析权益曲线的哈希值,以便标记数值上的偏差。

  • bench_common.py —— 结果导出的量化处理和时间区间压缩。

  • bench_data.py —— 数据源缓存 I/O 和 yfinance 数据重塑,仅针对固定版本的测试夹具。

  • bench_slippage.py —— 在成交量和波动率滑点模型下的向前分析。

向前分析基准测试运行在 tests/testdata/daily_1.pkl``(4 个品种,2 年日线数据,2020 行)之上,这与测试套件通过 ``tests/fixtures.py 使用的夹具相同;更大规模的场景则使用合成的 OHLCV 数据。WalkforwardColdWalkforwardProperCold 刻意承担 Numba 的 JIT 编译开销,因此切勿为二者添加预热(warmup)。

环境

asv.conf.json 使用 environment_type: virtualenv,因此每次提交都会在根据 setup.cfg 新建的虚拟环境中进行基准测试。安装命令为 python -mpip install -e .:不使用 Poetry,也不使用 tox,只用 pip。

pythons 列出了每个受支持的版本,并由 .github/scripts/check_python_versions.py.github/python-versions.json 保持同步。asv 会为其能找到解释器的每个条目构建一个环境,因此本地运行会覆盖已安装的那些版本;传入 --python 可以只选择其中一个:

asv run --python=3.12
asv continuous dev HEAD --python=3.12

CI 总是显式传入 --python,矩阵中的每个分支对应一个版本。

如果是本地临时性的基准测试,你可以将配置切换为 environment_type: existing``(使用当前已激活的虚拟环境),以跳过针对每次提交重建环境的步骤。如果修改了 ``asv.conf.json,请在提交前将其还原。

添加基准测试

benchmarks/ 目录下创建一个新文件(或在现有文件中添加一个类)。asv 会识别任何包含 time_peakmem_track_ 方法的类。setup 会在每个基准测试方法之前运行,teardown 则在之后运行。

有关参数化基准测试、超时设置和自定义跟踪指标的更多信息,请参阅 asv 编写基准测试指南