Commit 15821df
authored
Benchmark and performance improvements (#289)
This patch updates our benchmarks to be more focused and "micro" which
should make it easier to identify and address particular perf
bottlenecks. Only a couple of benchmarks have been added so far,
covering singular scalar fields, repeated scalar and message fields, and
repeated fields including a unique rule. The benchmarks can be run
consistently with `make bench` which has some args that may be
customized (see the Makefile).
---
This patch also includes a handful of performance improvements, focused
on heap usage (though there was a ~5% CPU time improvement):
- We use cel-go's `ReduceResiduals` to minimize/optimize the CEL
programs. This means the `rule` & `rules` globals variables used by
standard and predefined CEL expressions can be eliminated from the final
program (since the values we use from it are injected as constant
literals in the reduced AST). However, these globals were persisted in
the `cel.Env` which caused cel-go to allocate a composite Activation to
make them accessible alongside the `this` variable. Instead of using CEL
globals, this patch uses them as normal variables prior to computing
residuals, and elides them during actual execution of the CEL program,
avoiding the allocation.
- In order to keep `repeated.unique` `O(n)`, during validation we build
up a `map[T]struct{}{}` to check for uniqueness in the list. This rule
is particularly expensive, resulting in this map being allocated and
thrown away on every validation. While this rule could avoid allocations
altogether by making the comparison O(n^2) (effectively the CEL
expression `this.all(x, this.exists_one(y, x == y))`), I instead opted
to have the unique maps pull from a `sync.Pool`. Since the O(n^2) is
only an issue for large lists, in the future we could either use a
heuristic to swap between the CEL above or the map-based solution.
- `errors.As` ends up allocating when you take the double-pointer to the
target error, even when the source error is nil. (The escape analysis
can't see that far, unfortunately). Since the majority of the time
validation is successful, err is almost always nil. Performing a nil
check before calls to `errors.As` eliminates this allocation (albeit
small).
- For every call to `Validate`, we construct a config struct that's
drives the behavior of that single validation (things like fail-fast
mode, filtering, and the now CEL function). Typically, these are set
globally on the Validator instance itself, but can be overridden at
validation time. However, even if they weren't set at validation time,
we were still computing a new config object for every call, causing an
extra allocation. Now, the config is constructed only once with the
Validator and only copied and overwritten if validation time options are
provided.
These changes resulted in the following improvements on the (admittedly
limited) set of benchmarks added in
d716bad:
```
→ benchstat .tmp/bench/2025-11-18:12:58:39.bench.txt .tmp/bench/2025-11-18:13:01:39.bench.txt
goos: darwin
goarch: arm64:52:03.cpu.profile 2025-11-18:12:58:39.bench.txt 2025-11-18:12:58:39.mem.profile 2025-11-18:13:01:39.cpu.profile
pkg: buf.build/go/protovalidate
cpu: Apple M1 Max
│ .tmp/bench/2025-11-18:12:58:39.bench.txt │ .tmp/bench/2025-11-18:13:01:39.bench.txt │
│ sec/op │ sec/op vs base │
Scalar-10 421.8n ± 1% 396.0n ± 3% -6.12% (p=0.000 n=10)
Repeated/Scalar-10 480.5n ± 1% 455.0n ± 2% -5.30% (p=0.001 n=10)
Repeated/Message-10 607.0n ± 1% 561.2n ± 1% -7.55% (p=0.000 n=10)
Repeated/Unique/Scalar-10 735.4n ± 3% 686.2n ± 2% -6.68% (p=0.000 n=10)
Repeated/Unique/Bytes-10 987.1n ± 4% 933.9n ± 3% -5.39% (p=0.000 n=10)
geomean 616.8n 578.5n -6.21%
│ .tmp/bench/2025-11-18:12:58:39.bench.txt │ .tmp/bench/2025-11-18:13:01:39.bench.txt │
│ B/op │ B/op vs base │
Scalar-10 72.00 ± 0% 0.00 ± 0% -100.00% (p=0.000 n=10)
Repeated/Scalar-10 192.0 ± 0% 120.0 ± 0% -37.50% (p=0.000 n=10)
Repeated/Message-10 256.0 ± 0% 120.0 ± 0% -53.12% (p=0.000 n=10)
Repeated/Unique/Scalar-10 1064.0 ± 0% 536.0 ± 0% -49.62% (p=0.000 n=10)
Repeated/Unique/Bytes-10 2.398Ki ± 0% 1.743Ki ± 0% -27.32% (p=0.000 n=10)
geomean 391.9 ? ¹ ²
¹ summaries must be >0 to compute geomean
² ratios must be >0 to compute geomean
│ .tmp/bench/2025-11-18:12:58:39.bench.txt │ .tmp/bench/2025-11-18:13:01:39.bench.txt │
│ allocs/op │ allocs/op vs base │
Scalar-10 3.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=10)
Repeated/Scalar-10 6.000 ± 0% 3.000 ± 0% -50.00% (p=0.000 n=10)
Repeated/Message-10 8.000 ± 0% 3.000 ± 0% -62.50% (p=0.000 n=10)
Repeated/Unique/Scalar-10 40.00 ± 0% 34.00 ± 0% -15.00% (p=0.000 n=10)
Repeated/Unique/Bytes-10 88.00 ± 0% 73.00 ± 0% -17.05% (p=0.000 n=10)
geomean 13.84 ? ¹ ²
¹ summaries must be >0 to compute geomean
² ratios must be >0 to compute geomean
```1 parent 2473f44 commit 15821df
File tree
18 files changed
+967
-131
lines changed- cel
- internal/gen/tests/example/v1
- proto/tests/example/v1
18 files changed
+967
-131
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
24 | 25 | | |
25 | 26 | | |
26 | 27 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
9 | | - | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
10 | 12 | | |
11 | 13 | | |
12 | 14 | | |
| |||
94 | 96 | | |
95 | 97 | | |
96 | 98 | | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
97 | 112 | | |
98 | 113 | | |
99 | 114 | | |
100 | 115 | | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
101 | 119 | | |
102 | 120 | | |
103 | 121 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
25 | 24 | | |
26 | 25 | | |
27 | 26 | | |
| |||
41 | 40 | | |
42 | 41 | | |
43 | 42 | | |
44 | | - | |
| 43 | + | |
45 | 44 | | |
46 | 45 | | |
47 | 46 | | |
| |||
52 | 51 | | |
53 | 52 | | |
54 | 53 | | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
55 | 59 | | |
56 | | - | |
| 60 | + | |
57 | 61 | | |
58 | | - | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
59 | 67 | | |
60 | 68 | | |
61 | 69 | | |
62 | 70 | | |
63 | 71 | | |
64 | 72 | | |
65 | | - | |
| 73 | + | |
66 | 74 | | |
67 | 75 | | |
68 | 76 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
94 | | - | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
95 | 98 | | |
96 | 99 | | |
97 | 100 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
| 8 | + | |
7 | 9 | | |
8 | 10 | | |
9 | 11 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| 6 | + | |
6 | 7 | | |
7 | 8 | | |
8 | 9 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
100 | 100 | | |
101 | 101 | | |
102 | 102 | | |
103 | | - | |
104 | | - | |
| 103 | + | |
105 | 104 | | |
106 | 105 | | |
107 | 106 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
| 25 | + | |
25 | 26 | | |
26 | 27 | | |
27 | 28 | | |
| |||
49 | 50 | | |
50 | 51 | | |
51 | 52 | | |
52 | | - | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
53 | 61 | | |
54 | 62 | | |
55 | 63 | | |
| |||
59 | 67 | | |
60 | 68 | | |
61 | 69 | | |
62 | | - | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
63 | 74 | | |
64 | | - | |
| 75 | + | |
65 | 76 | | |
66 | 77 | | |
67 | 78 | | |
| |||
375 | 386 | | |
376 | 387 | | |
377 | 388 | | |
378 | | - | |
| 389 | + | |
379 | 390 | | |
380 | 391 | | |
381 | 392 | | |
382 | 393 | | |
383 | 394 | | |
384 | 395 | | |
385 | 396 | | |
386 | | - | |
| 397 | + | |
387 | 398 | | |
388 | 399 | | |
389 | 400 | | |
| |||
398 | 409 | | |
399 | 410 | | |
400 | 411 | | |
401 | | - | |
| 412 | + | |
402 | 413 | | |
403 | 414 | | |
404 | 415 | | |
405 | 416 | | |
406 | 417 | | |
407 | 418 | | |
408 | 419 | | |
409 | | - | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
410 | 425 | | |
411 | 426 | | |
412 | 427 | | |
| |||
421 | 436 | | |
422 | 437 | | |
423 | 438 | | |
424 | | - | |
| 439 | + | |
425 | 440 | | |
426 | 441 | | |
427 | 442 | | |
428 | 443 | | |
429 | 444 | | |
430 | 445 | | |
431 | 446 | | |
432 | | - | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
433 | 452 | | |
434 | 453 | | |
435 | | - | |
436 | | - | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
437 | 457 | | |
438 | | - | |
| 458 | + | |
| 459 | + | |
439 | 460 | | |
440 | 461 | | |
441 | | - | |
| 462 | + | |
442 | 463 | | |
443 | 464 | | |
444 | 465 | | |
| |||
0 commit comments