Skip to content

GROOVY-10307: Add targeted JMH benchmarks for SwitchPoint invalidation#2390

Merged
paulk-asert merged 2 commits intoapache:masterfrom
jamesfredley:groovy-10307-grails-like-benchmarks
Mar 4, 2026
Merged

GROOVY-10307: Add targeted JMH benchmarks for SwitchPoint invalidation#2390
paulk-asert merged 2 commits intoapache:masterfrom
jamesfredley:groovy-10307-grails-like-benchmarks

Conversation

@jamesfredley
Copy link
Contributor

@jamesfredley jamesfredley commented Mar 2, 2026

Summary

Adds three new JMH benchmark classes in org.apache.groovy.perf.grails that reproduce the real-world invokedynamic performance regression observed in Grails 7 applications (GROOVY-10307).

Benchmark Classes

CallSiteInvalidationBench (11 benchmarks)

Tests the core SwitchPoint invalidation mechanism - the root cause of the regression. Demonstrates that modifying ANY metaclass triggers global invalidation affecting ALL call sites:

  • Cross-type invalidation at 3 frequencies (every 100/1000/10000 iterations)
  • Same-type invalidation for comparison
  • Multiple call sites scaling (5 concurrent call sites)
  • Burst-then-steady-state simulating framework startup

Key result: baselineHotLoop (0.49ms) vs crossTypeInvalidationEvery1000 (467ms) = ~950x overhead

MetaclassVariationBench (9 benchmarks)

Tests GORM-like patterns where domain classes get per-instance ExpandoMetaClass enhancements:

  • Shared vs per-instance metaclass dispatch
  • Multi-class startup simulation (4 domain types enhanced in sequence)
  • Dynamic finder calls via static metaclass injection
  • Per-instance EMC with ongoing churn (worst case)

Key result: baselineSharedMetaclass (2.0ms) vs perInstanceMetaclass (420ms) = ~207x overhead

GrailsWorkloadBench (14 benchmarks)

Tests patterns extracted from the grails7-performance-regression demo app's PerformanceTestService:

  • Collection closure chains (.findAll{}.collect{}.groupBy{}.collectEntries{})
  • Spread operator (employees*.firstName)
  • Nested closure delegation (3-level criteria-like DSL)
  • GString interpolation with dynamic property access
  • Dynamic property by name (this."$field")
  • Project metrics aggregation (.count{}, .sum{}, map building)
  • Full analysis combining all patterns

Key result: baselineCollectionClosureChain (199ms) vs with invalidation (1631ms) = ~8.2x overhead (matches demo app's observed 8.2x bootRun regression)

Running

# All new benchmarks
./gradlew :performance:jmh -PbenchInclude="CallSiteInvalidation|MetaclassVariation|GrailsWorkload"

# Individual benchmark class
./gradlew :performance:jmh -PbenchInclude=CallSiteInvalidation

Related

@jamesfredley jamesfredley deleted the groovy-10307-grails-like-benchmarks branch March 2, 2026 22:44
@jamesfredley jamesfredley restored the groovy-10307-grails-like-benchmarks branch March 2, 2026 23:57
@jamesfredley jamesfredley reopened this Mar 3, 2026
@jamesfredley jamesfredley force-pushed the groovy-10307-grails-like-benchmarks branch 2 times, most recently from 2cda0d5 to 46bf5e8 Compare March 3, 2026 00:17
@jamesfredley jamesfredley force-pushed the groovy-10307-grails-like-benchmarks branch from 46bf5e8 to e910285 Compare March 3, 2026 00:23
@jamesfredley jamesfredley changed the title GROOVY-10307: Add targeted JMH benchmarks for SwitchPoint invalidation regression GROOVY-10307: Add targeted JMH benchmarks for SwitchPoint invalidation Mar 3, 2026
@codecov-commenter
Copy link

codecov-commenter commented Mar 3, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 66.7468%. Comparing base (8a40250) to head (1af1c45).

Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##               master      #2390        +/-   ##
==================================================
+ Coverage     66.7450%   66.7468%   +0.0017%     
- Complexity      29848      29849         +1     
==================================================
  Files            1382       1382                
  Lines          116130     116130                
  Branches        20471      20471                
==================================================
+ Hits            77511      77513         +2     
+ Misses          32286      32284         -2     
  Partials         6333       6333                

see 5 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jamesfredley jamesfredley marked this pull request as ready for review March 3, 2026 12:45
Copilot AI review requested due to automatic review settings March 3, 2026 12:45
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds new JMH benchmarks under org.apache.groovy.perf.grails to reproduce and quantify the invokedynamic regression related to global SwitchPoint invalidation seen in Grails 7 apps (GROOVY-10307), focusing on metaclass churn and Grails-like workloads.

Changes:

  • Introduces CallSiteInvalidationBench to measure cross-type vs same-type invalidation and multi-call-site scaling.
  • Introduces MetaclassVariationBench to model GORM-like per-instance ExpandoMetaClass behavior and static metaclass injection patterns.
  • Introduces GrailsWorkloadBench to model higher-level Grails workload patterns (closure chains, delegation, GStrings, dynamic property access) with periodic invalidation.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/grails/CallSiteInvalidationBench.groovy New focused benchmarks for SwitchPoint invalidation impact on hot call sites.
subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/grails/MetaclassVariationBench.groovy New benchmarks for shared vs per-instance metaclass dispatch and GORM-like injection patterns.
subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/grails/GrailsWorkloadBench.groovy New workload-style benchmarks derived from Grails demo regression patterns, with/without invalidation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Remove unused ExpandoMetaClass import from CallSiteInvalidationBench.

Clarify Javadoc on dynamicFinderCalls and mixedCompiledAndDynamicFinders
to state that injection cost is intentionally included since these model
per-request GORM behavior.

Replace System.nanoTime() with a deterministic counter for metaclass
property name variation in fullAnalysisWithInvalidation to eliminate
unrelated overhead and improve run-to-run comparability.
@paulk-asert paulk-asert merged commit a6a83ab into apache:master Mar 4, 2026
22 checks passed
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.

4 participants