Skip to content

Speed up cascading, container elements and executable validation - #277

Open
jungm wants to merge 4 commits into
perf-4-validation-planfrom
perf-5-cascading-executables
Open

jungm wants to merge 4 commits into
perf-4-validation-planfrom
perf-5-cascading-executables

Conversation

@jungm

@jungm jungm commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

Summary

Removes the remaining overhead in cascading, container elements and executable validation.

 SproutFrame.recurse(groups)
-  groups.applyTo(callback)
+  single group without conversions: cascade directly
   cascade
-    unwrap composed container element descriptors (lambda) per value
+    precomputed flat array
-    resolve the runtime container key reflectively per container
+    cached per container class
   ContainerElementCascadeFrame.getMultiplexContext
-    copy the path, remove the leaf, compare against each ancestor's copied path
+    compare paths in place
   multiplexEach
-    capturing lambda per cascaded value
+    direct call
 ValidateParameters / ValidateReturnValue
-  getConstraintsForMethod(name, types)  # new Signature hashed twice per call
+  getConstraintsForExecutable(executable)  # cached per Executable
+  reuse base path, Default redefinition; no streams; parameter names cached

Evidence

  • Before / After: benchmarks

Numbers are JMH throughput (-p provider=bval -f 2 -wi 2 -i 3, 1 s iterations, -prof gc, JDK 25), measured against the PR below this one. The machine was busy, so throughput carries wide error bars; the bytes allocated per operation (B/op) are deterministic.

Benchmark Before After Change B/op before → after
executableParameters (ops/ms) 564 ± 52% 2,125 ± 11% 3.77× 3,689 → 1,717
cascadedCycles (ops/ms) 5.52 ± 74% 17.5 ± 13% 3.16× 443,567 → 214,013
cascadedList (ops/ms) 1.57 ± 23% 4.36 ± 10% 2.78× 2,043,592 → 882,730
deepGraph (ops/ms) 345 ± 45% 850 ± 55% 2.46× 4,008 → 3,360
containerElements (ops/ms) 4.99 ± 80% 11.4 ± 27% 2.29× 391,370 → 283,097
executableReturnValue (ops/ms) 1,258 ± 60% 2,226 ± 23% 1.77× 1,817 → 1,401
All benchmarks
Benchmark Before After Change B/op before → after
cascadedCycles (ops/ms) 5.52 ± 74% 17.5 ± 13% 3.16× 443,567 → 214,013
cascadedList (ops/ms) 1.57 ± 23% 4.36 ± 10% 2.78× 2,043,592 → 882,730
classLevelInvalid (ops/ms) 2,780 ± 53% 3,123 ± 21% 1.12× 1,232 → 1,232
classLevelValid (ops/ms) 5,027 ± 34% 6,903 ± 22% 1.37× 568 → 568
composedInvalid (ops/ms) 2,016 ± 97% 2,729 ± 33% 1.35× 1,232 → 1,232
composedValid (ops/ms) 2,832 ± 83% 3,925 ± 43% 1.39× 800 → 852
containerElements (ops/ms) 4.99 ± 80% 11.4 ± 27% 2.29× 391,370 → 283,097
deepGraph (ops/ms) 345 ± 45% 850 ± 55% 2.46× 4,008 → 3,360
elMessage (ops/ms) 1,154 ± 44% 1,009 ± 28% 0.87× 2,088 → 2,096
executableParameters (ops/ms) 564 ± 52% 2,125 ± 11% 3.77× 3,689 → 1,717
executableReturnValue (ops/ms) 1,258 ± 60% 2,226 ± 23% 1.77× 1,817 → 1,401
explicitGroup (ops/ms) 4,462 ± 53% 5,199 ± 11% 1.17× 698 → 698
mixedDrivers (ops/ms) 3,588 ± 4% 3,530 ± 9% 0.98× 1,041 → 1,041
validateProperty (ops/ms) 2,983 ± 10% 3,009 ± 5% 1.01× 1,316 → 1,292
validateValue (ops/ms) 6,487 ± 9% 5,899 ± 27% 0.91× 648 → 636
wideBeanInvalid (ops/ms) 267 ± 11% 197 ± 88% 0.74× 9,568 → 9,528
wideBeanValid (ops/ms) 825 ± 10% 720 ± 17% 0.87× 2,816 → 2,816
Jsr303Benchmark.bvalBootstrapAndValidate (ops/s) 2,880 ± 56% 2,284 ± 51% 0.79× 247,317 → 248,388
Jsr303Benchmark.bvalBuildFactory (ops/s) 11,763 ± 3% 11,070 ± 12% 0.94× 88,397 → 88,753
Jsr303Benchmark.bvalConstraintsFailure (ops/s) 3,370,311 ± 10% 2,849,765 ± 20% 0.85× 1,352 → 1,352
Jsr303Benchmark.bvalConstraintsFailureReuseValidator (ops/s) 2,876,706 ± 16% 3,121,360 ± 4% 1.09× 1,376 → 1,384
Jsr303Benchmark.bvalConstraintsSuccess (ops/s) 13,015,631 ± 35% 12,273,725 ± 29% 0.94× 432 → 432
Jsr303Benchmark.bvalConstraintsSuccessReuseValidator (ops/s) 13,263,502 ± 19% 14,058,074 ± 9% 1.06× 432 → 432
Jsr303Benchmark.bvalNoConstraints (ops/s) 167,339,787 ± 11% 158,038,315 ± 11% 0.94× 0 → 0
Jsr303Benchmark.bvalNoConstraintsReuseValidator (ops/s) 187,581,916 ± 24% 186,538,080 ± 12% 0.99× 0 → 0

A few rows (wideBeanInvalid, elMessage) are lower in this run with unchanged allocation, which points to noise.

  • Tests: bval-jsr unit tests: 255 run, 0 failures · TCK: 1070 run, 0 failures, 1 skipped

Merge Danger

Door: two-way

Blast Radius: cascading

  • Paths of cascaded container elements are built by a different route; the TCK's container element tests cover the expected paths.
  • Only DefaultParameterNameProvider caches names; custom providers are called as before.
  • The executable descriptor cache holds one entry per validated Executable per bean descriptor.

@jungm
jungm added this pull request to stack #278 October 5, 2026 11:20
jungm added 4 commits October 5, 2026 13:32
…keys

- Precompute the leaf container element descriptors of an element as a
  flat array instead of unwrapping composed descriptors with a lambda for
  every validated value.
- Cache the runtime container key per container class on container
  element descriptors instead of resolving type arguments reflectively
  for every cascaded container.
- Collect extracted values without a Lazy holder.
Compare paths in place to find the context whose path is the element's
minus its container element node, without materializing the element's
path when it is its parent's plus one node.
Cascade a single-group strategy without group conversions directly
instead of through GroupStrategy#applyTo, and into container elements
without allocating a capturing consumer per cascaded value.
- Cache executable descriptors per executable instead of hashing a new
  Signature for every call, and resolve them once per validation.
- Reuse the executable's base path and the bean's Default group
  redefinition, and create parameter frames without streams.
- Cache default parameter names per executable.
- Map wrapper types to primitives with a lookup rather than a scan.
@jungm
jungm force-pushed the perf-5-cascading-executables branch from 944a989 to d0bfa4c Compare October 5, 2026 11:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant