GROOVY-12285: STC: index extension methods by name and skip cloning non-generic parameters - #2823
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2823 +/- ##
==================================================
+ Coverage 70.2594% 70.2702% +0.0108%
- Complexity 36274 36284 +10
==================================================
Files 1569 1569
Lines 133723 133765 +42
Branches 24637 24648 +11
==================================================
+ Hits 93953 93997 +44
+ Misses 31257 31255 -2
Partials 8513 8513
🚀 New features to boost your workflow:
|
JMH summary — classic (commit
|
| Group | Speedup | Calibrated | n |
|---|---|---|---|
| bench | 1.018 × | 1.040 × | 99 |
| core | 1.018 × | 1.049 × | 83 |
| grails | 1.151 × | 1.173 × | 80 |
⚠️ 1 benchmark at least 1.5× slower than the 90-day baseline:
org.apache.groovy.perf.MethodInvocationBench.benchmarkMethodWithParams— 2.98× slower (calibrated)
Runner calibration (this run vs baseline hardware): bench 0.99× (26 rulers) · core-ag 1.01× (3 rulers) · core-hz 0.92× (3 rulers) · grails-ad 1.01× (3 rulers) · grails-ez 0.96× (3 rulers)
Baseline: dev/bench/jmh/<part>/classic/data.js on gh-pages, trailing 90 days. Daily dashboard · Per-suite raw data
JMH summary — indy (commit
|
| Group | Speedup | Calibrated | n |
|---|---|---|---|
| bench | 0.982 × | 1.251 × | 99 |
| core | 11.391 × | 9.053 × | 83 |
| grails | 5.377 × | 4.961 × | 80 |
No benchmark is ≥1.5× slower than its 90-day baseline.
⚠️ Runner speed differs ≥15% from the historical baseline hardware for: bench, core-ag, grails-ez. Raw speedups are not meaningful for those parts — use the calibrated column.
Runner calibration (this run vs baseline hardware): bench 0.83× (26 rulers) · core-ag 1.45× (3 rulers) · core-hz 1.05× (3 rulers) · grails-ad 0.96× (3 rulers) · grails-ez 1.20× (3 rulers)
Baseline: dev/bench/jmh/<part>/indy/data.js on gh-pages, trailing 90 days. Daily dashboard · Per-suite raw data
5016733 to
9f2e622
Compare
There was a problem hiding this comment.
Pull request overview
This PR optimizes Groovy’s Static Type Checker (STC) hot paths by (1) indexing extension methods by name to avoid repeated linear scans during DGM lookup and (2) avoiding unnecessary Parameter[] cloning during overload distance measurement when no generic erasure is required. It also adds targeted regression tests plus JMH benchmarks to validate and quantify the performance improvements.
Changes:
- Add per-receiver, name-indexed extension-method lookup via
ExtensionMethodCache/AbstractExtensionMethodCacheintegration. - Introduce a copy-on-write
parametersForDistancefast path to skip cloning non-generic parameter arrays duringchooseBestMethod. - Add regression tests and JMH micro/macro benchmarks covering DGM lookup and overload selection behavior/performance.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/main/java/org/codehaus/groovy/transform/stc/AbstractExtensionMethodCache.java |
Wrap cached per-receiver method lists in an immutable list that also provides O(1) lookup by name; add proper invalidation APIs for derived caches. |
src/main/java/org/codehaus/groovy/transform/stc/ExtensionMethodCache.java |
Update cache documentation to reflect name-indexed lookup behavior. |
src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java |
Switch DGM lookup to name-indexed cache access; add parametersForDistance to avoid unnecessary cloning; improve cache-clearing semantics via new invalidation APIs. |
src/test/groovy/org/codehaus/groovy/transform/stc/DgmMethodLookupTest.groovy |
Add regression tests validating correctness, stability, and immutability of the name-indexed DGM lookup (including cache invalidation scenarios). |
src/test/groovy/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupportTest.groovy |
Add tests ensuring chooseBestMethod does not mutate method parameters and correctly erases generics/placeholder/array cases without modifying the original method node. |
subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/DgmMethodLookupBench.java |
Add JMH microbenchmark to measure DGM lookup performance across receiver types and hit/miss scenarios. |
subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/ChooseBestMethodBench.java |
Add JMH microbenchmark to measure allocation/time impact of chooseBestMethod changes across non-generic/generic/mixed overload sets. |
subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/StcCompilePerfBench.java |
Add JMH macrobenchmark compiling generated @CompileStatic sources to measure end-to-end STC throughput/latency changes. |
ARCHITECTURE.md |
Document the extension-method caching and name indexing at a high level. |
Suppressed comments (1)
subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/ChooseBestMethodBench.java:26
- Unused import
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport; the benchmark callschooseBestMethodvia static import.
import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport;
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
AI read:
|
…on-generic parameters
9f2e622 to
850977f
Compare
|
@paulk-asert All feedback has been carefully analyzed and addressed in this update. Below is the point-by-point response and a summary of the refinements made. 1. Addressing the Two Requested Changes(a) Eliminating the Brittle Downcast in
|
|
✅ All tests passed ✅🏷️ Commit: 850977f Learn more about TestLens at testlens.app/docs. |
|
@daniellansun — I ran JAIPilot Cloud against the earlier evaluated head bd48dbe. It produced an AI-generated draft that removes two Checkstyle-proven unused ClassHelper imports from the new JMH benchmarks: skrcode#3 The upstream head has since moved to 850977f, so this should not be applied blindly; it needs rebasing or may already be obsolete. This is a review aid only; nothing is auto-merged. |



https://issues.apache.org/jira/browse/GROOVY-12285
Performance Verification Report: GROOVY-12285
Title: STC: Index Extension Methods by Name and Skip Cloning Non-Generic Parameters
Evaluated Commit:
90fc4e8dcd5f1c85b7e1403c46e3ef46f0f4c07e(GROOVY-12285)Baseline Commit:
f6bc5a511c3ea79e51afe0eb26e494e544da06c1(origin/master)Target Component: Static Type Checker (STC) & AST Transformation Support
Environment: OpenJDK 64-Bit Server VM (Amazon Corretto 25.0.2+10-LTS), Linux x86_64
1. Executive Summary
This report delivers an empirical and architectural performance verification of commit
90fc4e8dcd5f1c85b7e1403c46e3ef46f0f4c07e(GROOVY-12285), which targets two major compile-time performance bottlenecks within Apache Groovy's Static Type Checker (STC):MethodsByName).Parameter[].clone()allocations for candidate methods with concrete (non-generic) parameter lists inStaticTypeCheckingSupport.chooseBestMethod.Key Benchmark Findings
f6bc5a511c)90fc4e8dcd)Stringextension method hit (lookupStringHit)Listextension method miss (lookupListMiss)Objectextension method miss (lookupObjectMiss)chooseHeavyNonGeneric)chooseNonGeneric)dgmDense)2. Technical Architecture & Bytecode Analysis
2.1 Optimization 1: Name-Indexed Extension Method Cache (
MethodsByName)Problem in Baseline (
f6bc5a511c)During static type checking (
@CompileStaticor@TypeChecked), every method call expression triggers extension method lookup across the receiver's type hierarchy viaStaticTypeCheckingSupport.findDGMMethodsForClassNode.In the baseline implementation:
AbstractExtensionMethodCachestored a map of typeMap<String, List<MethodNode>>.java.lang.Object, the list contains approximately 200 extension methods (e.g.,each,collect,find,findAll,grep,dump,inspect,identity,with,is,asBoolean,getAt,putAt, etc.).MethodNodein each list:accountService.processPayment(...)), the compiler scanned all ~200 methods onObjectand every interface node only to yield an empty set.Optimized Design (
90fc4e8dcd)MethodsByName, an immutableAbstractList<MethodNode>implementingRandomAccessthat encapsulates a pre-indexedMap<String, List<MethodNode>> byName:MethodNode[]) withCollections.singletonListfor singleton overloads avoids oversized list headers.List<MethodNode>contract for any external or legacy callers.2.2 Optimization 2: Parameter Array Cloning Elimination in
chooseBestMethodProblem in Baseline (
f6bc5a511c)In Phase 1 of
StaticTypeCheckingSupport.chooseBestMethod, the compiler measures the type distance between actual call arguments and formal method parameters.In the baseline:
INVOKEVIRTUAL [Lorg/codehaus/groovy/ast/Parameter;.clone()followed by aCHECKCAST.(String, int, boolean)). Unconditionally cloning parameter arrays for each candidate method across thousands of AST call sites created massive short-lived object churn in the Eden space.Optimized Design (
90fc4e8dcd)Refactored into
parametersForDistance(final MethodNode method)with lazy copy-on-write semantics:erasedremainsnulland the originalParameter[]reference is returned directly (0 bytes allocated).private static, allowing HotSpot C2 to inline it directly into the caller's loop.3. Benchmarking Methodology & Added Suites
To rigorously isolate and evaluate both micro-level operations and macro-level compilation impact, three dedicated JMH benchmark suites were implemented under
subprojects/performance:DgmMethodLookupBench(org.apache.groovy.perf.DgmMethodLookupBench):StaticTypeCheckingSupport.findDGMMethodsForClassNodeacross multiple receiver hierarchy shapes (Object,List,String,Map,String[],int[]).with,each,collect,padLeft,getAt) and misses (definitelyNotADgmMethod).AverageTime, unit:ns/op.ChooseBestMethodBench(org.apache.groovy.perf.ChooseBestMethodBench):chooseBestMethoddistance computation across concrete non-generic overloads, generic overloads, mixed overloads, and single-method candidates.-PjmhProfilers=gcto capture normalized allocation rates (gc.alloc.rate.norm, inB/op).AverageTime, unit:ns/op.StcCompilePerfBench(org.apache.groovy.perf.StcCompilePerfBench):@CompileStaticdown toPhases.INSTRUCTION_SELECTION.dgmDense: Intensive DGM extension method calls (each,collect,findAll,join,max,min,reverse,groupBy,inject,sum,padLeft).overloadDense: Intensive overloaded non-generic dispatch calls.mixedDense: Realistic mix of DGM calls, overloaded methods, and standard library calls.AverageTime, unit:ms/op.4. Empirical Benchmark Results
4.1 Microbenchmark 1: Extension Method Lookup (
DgmMethodLookupBench)Measured in nanoseconds per operation (
ns/op), lower is better. Confidence interval: 99.9%.Performance Insights:
ListorStringwastedStringandListreceiver lookups achieved up to4.2 Microbenchmark 2: Overload Distance & GC Allocation (
ChooseBestMethodBench)Measured with
-PjmhProfilers=gcunder JDK 25 64-bit (+UseCompressedOops).B/op)B/op)B/op)chooseSingleNonGenericchooseNonGenericOverloadschooseHeavyNonGenericchooseMixedOverloadschooseGenericOverloadsJVM Object Layout Verification:
chooseSingleNonGeneric):Parameter[2]array header (chooseHeavyNonGeneric):Parameter[3]array header (4.3 Macrobenchmark: STC Phase Compilation (
StcCompilePerfBench)Measured in milliseconds per compilation run (
ms/op), lower is better. Phase:INSTRUCTION_SELECTION.Compilation Analysis:
@CompileStaticcode where DGM extension calls are pervasive (dgmDense), the STC phase achieved a 19.3% end-to-end latency reduction (5. Architectural Evaluation & Verification Conclusion
MethodsByName) removes anDgmMethodLookupTestandStaticTypeCheckingSupportTest) pass with 100% success rate.Final Verdict: Commit
90fc4e8dcd5f1c85b7e1403c46e3ef46f0f4c07eis a mathematically sound, memory-efficient, and highly effective compiler optimization that significantly accelerates Groovy static compilation.