Skip to content

Improve sample generation performance by caching reflective type metadata - #15

Open
SvenWirz wants to merge 1 commit into
remondis-it:developfrom
SvenWirz:performance-type-model-caching
Open

SvenWirz wants to merge 1 commit into
remondis-it:developfrom
SvenWirz:performance-type-model-caching

Conversation

@SvenWirz

@SvenWirz SvenWirz commented Jul 7, 2026

Copy link
Copy Markdown

Summary

This PR removes repeated reflection work from the sample generation hot path:

  • Cached type metadata: introspected properties, accessible accessor methods and the public default constructor are now cached per type and CollectionSamplingMode in the new package-private TypeModel (see Properties.getTypeModel). Previously, Sample.newInstance() re-ran introspection and property filtering up to six times per generated instance - and recursively for every auto-sampled object in the graph.
  • Cached generic type resolution: ReflectionUtil.getCollectionType caches the resolved collection element type per read method instead of resolving getGenericReturnType() on every call.
  • Constructor reuse: auto-sampling reuses the cached default constructor instead of probing type.getConstructor() twice per generated object, and setAccessible(true) is no longer invoked on every single property write.

Behavioral notes:

  • Properties.getProperties now returns an unmodifiable set; the only mutating caller (Sample.getNotHitFields) copies it first.
  • Exception semantics are preserved (missing default constructor still raises SampleException / ReflectionException as before).
  • The caches are static and never evict - the same trade-off as the existing InvocationSensor proxy class cache.
  • A dedicated JaCoCo rule was added for TypeModel, following the existing pattern for internal classes with defensive code paths.

Benchmark

Adds a JMH benchmark (SampleGenerationBenchmark, test scope) covering flat bean generation, auto-sampling of an object graph (nested beans, collections, enum, map) and the configuration API including the field selector. Run it with:

mvn test-compile org.codehaus.mojo:exec-maven-plugin:3.1.0:exec -Dexec.executable=java "-Dexec.args=-classpath %classpath com.remondis.resample.benchmark.SampleGenerationBenchmark" -Dexec.classpathScope=test

Measured before/after on this branch (JDK 21, average time, 1 fork, 3 warmup + 5 measurement iterations):

Benchmark Before (us/op) After (us/op) Change
flatBean 1.483 +/- 0.026 1.235 +/- 0.029 -17%
objectGraphWithAutoSampling 16.293 +/- 0.163 11.688 +/- 0.199 -28%
configureAndCreateFlatBean 2.513 +/- 0.060 1.874 +/- 0.072 -25%

All 50 runnable unit tests pass; formatter validation and the JaCoCo coverage rules pass as well.

🤖 Generated with Claude Code

…data

- Cache introspected properties, accessible accessor methods and the
  default constructor per type and CollectionSamplingMode in the new
  TypeModel (Properties.getTypeModel), instead of re-introspecting up
  to six times per generated instance.
- Cache the resolved generic collection element type per read method
  in ReflectionUtil.getCollectionType.
- Reuse the cached default constructor in Sample instead of probing
  type.getConstructor() twice per auto-sampled object.
- Add a JMH benchmark (SampleGenerationBenchmark) measuring flat bean
  generation, auto-sampling of an object graph and the configuration
  API including the field selector.

JMH results (JDK 21, avgt, us/op):
- flatBean:                    1.483 -> 1.235 (-17%)
- objectGraphWithAutoSampling: 16.293 -> 11.688 (-28%)
- configureAndCreateFlatBean:  2.513 -> 1.874 (-25%)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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