Skip to content

Commit effb0de

Browse files
authored
DX: implement benchmark monitoring (#427)
1 parent 56914e8 commit effb0de

File tree

9 files changed

+135
-2
lines changed

9 files changed

+135
-2
lines changed

.github/workflows/benchmark.yml

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Benchmark
2+
env:
3+
PYTHONHASHSEED: "0"
4+
5+
on:
6+
push:
7+
branches:
8+
- main
9+
pull_request:
10+
branches:
11+
- main
12+
- epic/*
13+
workflow_dispatch:
14+
inputs:
15+
specific-pip-packages:
16+
description: Run benchmarks with specific pip packages
17+
required: false
18+
type: string
19+
20+
jobs:
21+
benchmark:
22+
name: Performance regression
23+
runs-on: ubuntu-22.04
24+
steps:
25+
- uses: actions/checkout@v4
26+
- uses: ComPWA/actions/pip-install@v1
27+
with:
28+
editable: "yes"
29+
extras: test,all
30+
python-version: "3.9"
31+
specific-packages: ${{ inputs.specific-pip-packages }}
32+
- name: Run pytest-benchmark
33+
run: |
34+
pytest \
35+
-k benchmark \
36+
--benchmark-json output.json \
37+
--durations=0
38+
working-directory: benchmarks
39+
- name: Store result
40+
if: github.event_name == 'push'
41+
uses: benchmark-action/github-action-benchmark@v1
42+
with:
43+
name: AmpForm benchmark results
44+
tool: pytest
45+
output-file-path: benchmarks/output.json
46+
github-token: ${{ secrets.GITHUB_TOKEN }}
47+
gh-pages-branch: benchmark-results
48+
benchmark-data-dir-path: ""
49+
auto-push: true
50+
- name: Warn on performance decrease
51+
if: github.event_name == 'pull_request'
52+
uses: benchmark-action/github-action-benchmark@v1
53+
with:
54+
name: AmpForm benchmark results
55+
tool: pytest
56+
output-file-path: benchmarks/output.json
57+
github-token: ${{ secrets.GITHUB_TOKEN }}
58+
gh-pages-branch: benchmark-results
59+
benchmark-data-dir-path: ""
60+
auto-push: false
61+
comment-on-alert: true
62+
fail-on-alert: true

.pre-commit-config.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ repos:
8989
- id: name-tests-test
9090
name: Tests should start with test_
9191
args: ["--django"]
92+
exclude: >
93+
(?x)^(
94+
benchmarks/.*
95+
)$
9296
- id: trailing-whitespace
9397

9498
- repo: https://github.com/pre-commit/mirrors-prettier

.vscode/settings.json

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"ruff.importStrategy": "fromEnvironment",
7474
"ruff.organizeImports": true,
7575
"search.exclude": {
76+
"**/benchmarks/**/__init__.py": true,
7677
"**/tests/**/__init__.py": true,
7778
".constraints/*.txt": true,
7879
"typings/**": true

benchmarks/__init__.py

Whitespace-only changes.

benchmarks/conftest.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from _pytest.config import Config
2+
3+
4+
def pytest_configure(config: Config):
5+
# cspell:ignore addinivalue
6+
config.addinivalue_line("python_files", "*.py")

benchmarks/doit_speed.py

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
import pytest
6+
import qrules
7+
8+
import ampform
9+
from ampform.dynamics.builder import create_relativistic_breit_wigner_with_ff
10+
11+
if TYPE_CHECKING:
12+
import sympy as sp
13+
from pytest_benchmark.fixture import BenchmarkFixture
14+
15+
16+
@pytest.mark.benchmark(group="doit", min_rounds=1)
17+
def test_doit_speed(benchmark: BenchmarkFixture):
18+
reaction = qrules.generate_transitions(
19+
initial_state=("psi(4160)", [-1, +1]),
20+
final_state=["D-", "D0", "pi+"],
21+
allowed_intermediate_particles=["D*(2007)0"],
22+
formalism="canonical-helicity",
23+
)
24+
builder = ampform.get_builder(reaction)
25+
for particle in reaction.get_intermediate_particles():
26+
builder.dynamics.assign(particle.name, create_relativistic_breit_wigner_with_ff)
27+
model = builder.formulate()
28+
29+
intensity_expr = benchmark(_perform_doit, model.expression)
30+
undefined_symbols = intensity_expr.free_symbols
31+
undefined_symbols -= set(model.parameter_defaults)
32+
undefined_symbols -= set(model.kinematic_variables)
33+
assert not undefined_symbols
34+
35+
36+
def _perform_doit(expr: sp.Expr):
37+
return expr.doit()

docs/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ hidden:
7777
maxdepth: 2
7878
---
7979
API <api/ampform>
80+
Continuous benchmarks <https://compwa.github.io/ampform>
8081
Changelog <https://github.com/ComPWA/ampform/releases>
8182
Upcoming features <https://github.com/ComPWA/ampform/milestones?direction=asc&sort=title&state=open>
8283
Help developing <https://compwa.github.io/develop>

pyproject.toml

+13-2
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ test = [
111111
"nbmake",
112112
"numpy",
113113
"pytest",
114+
"pytest-benchmark",
114115
"pytest-cov",
115116
"pytest-profiling",
116117
"pytest-xdist",
@@ -120,6 +121,7 @@ types = [
120121
"ipywidgets",
121122
"numpy",
122123
"pytest",
124+
"pytest-benchmark",
123125
"sphinx-api-relink >=0.0.3",
124126
]
125127
viz = ["graphviz"]
@@ -164,17 +166,21 @@ module = ["graphviz.*"]
164166

165167
[[tool.mypy.overrides]]
166168
ignore_missing_imports = true
167-
module = ["scipy.*"]
169+
module = ["pytest_benchmark.*"]
168170

169171
[[tool.mypy.overrides]]
170172
ignore_missing_imports = true
171173
module = ["ipywidgets.*"]
172174

175+
[[tool.mypy.overrides]]
176+
ignore_missing_imports = true
177+
module = ["scipy.*"]
178+
173179
[[tool.mypy.overrides]]
174180
check_untyped_defs = true
175181
disallow_incomplete_defs = false
176182
disallow_untyped_defs = false
177-
module = ["tests.*"]
183+
module = ["benchmarks.*", "tests.*"]
178184

179185
[[tool.mypy.overrides]]
180186
ignore_errors = true
@@ -253,6 +259,7 @@ norecursedirs = [
253259
"_build",
254260
]
255261
testpaths = [
262+
"benchmarks",
256263
"src",
257264
"tests",
258265
]
@@ -359,6 +366,10 @@ split-on-trailing-comma = false
359366
"T20",
360367
"TCH00",
361368
]
369+
"benchmarks/*" = [
370+
"D",
371+
"S101",
372+
]
362373
"docs/*" = [
363374
"E402",
364375
"INP001",

tox.ini

+11
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@ description =
1717
setenv =
1818
PYTHONHASHSEED = 0
1919

20+
[testenv:bench]
21+
allowlist_externals =
22+
pytest
23+
commands =
24+
pytest {posargs:benchmarks} \
25+
--durations=0 \
26+
--benchmark-autosave \
27+
-k benchmark
28+
description =
29+
Run benchmark tests and visualize in histogram
30+
2031
[testenv:cov]
2132
allowlist_externals =
2233
pytest

0 commit comments

Comments
 (0)