Skip to content

Commit e60fda7

Browse files
committed
Add comparison
1 parent f49fefd commit e60fda7

5 files changed

Lines changed: 44 additions & 20 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ __pycache__
44
# VS Code configuration
55
/.vscode/
66
.envrc
7+
.benchmarks

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,22 @@ Additionally, [protovalidate's core repository](https://github.com/bufbuild/prot
7575
- [Protovalidate's Protobuf API][validate-proto]
7676
- [Conformance testing utilities][conformance] for acceptance testing of `protovalidate` implementations
7777

78+
## Benchmarks
79+
80+
We include a set of benchmarks to verify different types of validation operations based on `pytest-benchmark`.
81+
You can run them with
82+
83+
```shellsession
84+
$ uv run poe bench
85+
```
86+
87+
This persists the runs results. It is often helpful to compare benchmark results between runs,
88+
for example before and after an optimization. To compare persisted results, use
89+
90+
```shellsession
91+
$ uv run poe bench-compare
92+
```
93+
7894
## Contributing
7995

8096
We genuinely appreciate any help! If you'd like to contribute, check out these resources:

poe_tasks.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ go run github.com/bufbuild/buf/private/pkg/licenseheader/cmd/license-header@v1.6
1717

1818
[tasks.bench]
1919
help = "Run benchmarks"
20-
cmd = "pytest --benchmark-enable --benchmark-only"
20+
cmd = "pytest --benchmark-enable --benchmark-only --benchmark-autosave"
21+
22+
[tasks.bench-compare]
23+
help = "Compare saved benchmark runs"
24+
cmd = "pytest-benchmark compare --group-by=func,param:_id --sort=name"
2125

2226
[tasks.check]
2327
help = "Run code checks"

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ raw-options = { fallback_version = "0.0.0" }
6767
[tool.pytest]
6868
addopts = [
6969
"--benchmark-disable",
70+
"--benchmark-group-by=func,param:_id",
7071
]
7172
# Turn all warnings into errors,
7273
# except DeprecationWarnings (which we knowingly tolerate due to using old `protobuf` APIs).

test/test_benchmark.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -113,23 +113,25 @@ def gen_complex(depth: int) -> BenchComplexSchema:
113113
validator = protovalidate.Validator()
114114

115115

116+
def param(*args, id: str) -> pytest.param: # noqa: A002
117+
"""Copies pytest id to a fixture parameter since pytest-benchmark doesn't allow
118+
grouping on the former."""
119+
return pytest.param(id, *args, id=id)
120+
121+
116122
# Use lambda factories to allow random seed fixture to apply before computing
117123
cases = [
118-
pytest.param(lambda: BenchScalar(x=42), id="scalar"),
119-
pytest.param(lambda: BenchRepeatedScalar(x=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), id="repeated_scalar"),
120-
pytest.param(lambda: BenchRepeatedMessage(x=[BenchScalar(x=i + 1) for i in range(10)]), id="repeated_message"),
121-
pytest.param(
122-
lambda: BenchRepeatedScalarUnique(x=[1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8]), id="repeated_unique_scalar"
123-
),
124-
pytest.param(
125-
lambda: BenchRepeatedBytesUnique(x=[gen_bytes(4, i + 1) for i in range(8)]), id="repeated_unique_bytes"
126-
),
127-
pytest.param(
124+
param(lambda: BenchScalar(x=42), id="scalar"),
125+
param(lambda: BenchRepeatedScalar(x=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), id="repeated_scalar"),
126+
param(lambda: BenchRepeatedMessage(x=[BenchScalar(x=i + 1) for i in range(10)]), id="repeated_message"),
127+
param(lambda: BenchRepeatedScalarUnique(x=[1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8]), id="repeated_unique_scalar"),
128+
param(lambda: BenchRepeatedBytesUnique(x=[gen_bytes(4, i + 1) for i in range(8)]), id="repeated_unique_bytes"),
129+
param(
128130
lambda: BenchMap(entries={"k1": "v1", "k2": "v2", "k3": "v3", "k4": "v4", "k5": "v5", "k6": "v6", "k7": "v7"}),
129131
id="map",
130132
),
131-
pytest.param(lambda: gen_complex(1), id="complex_schema"),
132-
pytest.param(
133+
param(lambda: gen_complex(1), id="complex_schema"),
134+
param(
133135
lambda: BenchGT(
134136
gt=50,
135137
gte=50,
@@ -150,7 +152,7 @@ def gen_complex(depth: int) -> BenchComplexSchema:
150152
),
151153
id="int32_gt",
152154
),
153-
pytest.param(
155+
param(
154156
lambda: ByteMatching(
155157
# 16-byte buffers; bytes.ip accepts 4 or 16 bytes (v4/v6 raw), bytes.ipv4
156158
# requires 4 bytes, bytes.ipv6 requires 16, bytes.uuid requires 16.
@@ -161,7 +163,7 @@ def gen_complex(depth: int) -> BenchComplexSchema:
161163
),
162164
id="bytes_matching",
163165
),
164-
pytest.param(
166+
param(
165167
lambda: StringMatching(
166168
hostname="example.com",
167169
host_and_port="example.com:8080",
@@ -170,7 +172,7 @@ def gen_complex(depth: int) -> BenchComplexSchema:
170172
),
171173
id="string_matching",
172174
),
173-
pytest.param(
175+
param(
174176
lambda: WrapperTesting(
175177
i32=Int32Value(value=11),
176178
d=DoubleValue(value=11),
@@ -184,12 +186,12 @@ def gen_complex(depth: int) -> BenchComplexSchema:
184186
),
185187
id="wrapper_testing",
186188
),
187-
pytest.param(lambda: MultiRule(many=1), id="multi_rule_error"),
188-
pytest.param(lambda: MultiRule(many=10), id="multi_rule_no_error"),
189+
param(lambda: MultiRule(many=1), id="multi_rule_error"),
190+
param(lambda: MultiRule(many=10), id="multi_rule_no_error"),
189191
]
190192

191193

192-
@pytest.mark.parametrize("message_factory", cases)
193-
def test_benchmark(message_factory: Callable[[], Message], benchmark: BenchmarkFixture):
194+
@pytest.mark.parametrize(("_id", "message_factory"), cases)
195+
def test_benchmark(_id: str, message_factory: Callable[[], Message], benchmark: BenchmarkFixture):
194196
message = message_factory()
195197
benchmark(validator.collect_violations, message)

0 commit comments

Comments
 (0)