GROOVY-10307: Groovy 6 - Improve invokedynamic performance with optimized caching#2377
GROOVY-10307: Groovy 6 - Improve invokedynamic performance with optimized caching#2377jamesfredley wants to merge 2 commits intoapache:masterfrom
Conversation
Change @InputFiles @classpath to @input on untouchedFiles field. This field contains glob patterns (strings), not actual files, so @InputFiles and @classpath were inappropriate and caused Gradle to treat patterns containing '*' as literal file paths on Windows.
Reduce the performance impact of metaclass changes on invokedynamic call sites: - Disable global SwitchPoint guard by default (controlled via groovy.indy.switchpoint.guard) - Track all call sites via WeakReference set for targeted invalidation - Add clearCache() method to CacheableCallSite for cache invalidation on metaclass change - When metaclass changes, clear caches and reset targets on all registered call sites This provides ~19% improvement on metaclass invalidation stress tests compared to baseline Groovy 6.x, and ~24% improvement compared to Groovy 4.0.30.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #2377 +/- ##
==================================================
+ Coverage 68.3341% 68.3418% +0.0077%
- Complexity 30319 30327 +8
==================================================
Files 1382 1382
Lines 116087 116109 +22
Branches 20464 20467 +3
==================================================
+ Hits 79327 79351 +24
+ Misses 30261 30259 -2
Partials 6499 6499
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This pull request improves invokedynamic performance in Groovy 6 by replacing the global SwitchPoint invalidation mechanism with targeted per-call-site cache invalidation. When metaclasses change in Groovy, instead of invalidating ALL call sites across the application, only the cached method handles at each call site are cleared, significantly reducing overhead in applications that frequently modify metaclasses.
Changes:
- Introduced a call site registry using WeakReferences to track all active call sites for targeted invalidation
- Made the global SwitchPoint guard optional (disabled by default) via the
groovy.indy.switchpoint.guardsystem property - Added cache clearing mechanism to CacheableCallSite for metaclass change handling
- Fixed a Windows build issue in JarJarTask by correcting the annotation for glob pattern strings
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/main/java/org/codehaus/groovy/vmplugin/v8/Selector.java | Made global SwitchPoint guard conditional via INDY_SWITCHPOINT_GUARD property (disabled by default) |
| src/main/java/org/codehaus/groovy/vmplugin/v8/IndyInterface.java | Added WeakReference-based call site registry and targeted cache invalidation logic |
| src/main/java/org/codehaus/groovy/vmplugin/v8/CacheableCallSite.java | Added clearCache() method to clear LRU cache and reset fallback count on metaclass changes |
| build-logic/src/main/groovy/org/apache/groovy/gradle/JarJarTask.groovy | Fixed annotation from @InputFiles @classpath to @input for glob pattern strings |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
Based on #2374, but applied to master(Groovy 6) instead of GROOVY_4_0_X
This PR improves invokedynamic performance reported in GROOVY-10307. The optimization reduces the performance impact of metaclass changes on call sites by replacing the global SwitchPoint invalidation mechanism with targeted per-call-site cache invalidation.
Problem
When any metaclass changes in Groovy, the global
SwitchPointis invalidated, causing all invokedynamic call sites across the entire application to fall back and re-link. This creates significant overhead in applications that frequently modify metaclasses (e.g., Grails applications with dynamic finders, runtime mixins, etc.).Solution
This PR implements a more targeted invalidation strategy:
Disable global SwitchPoint guard by default - The
SwitchPoint.guardWithTest()wrapper is now optional and disabled by default. This prevents mass invalidation of all call sites when any metaclass changes.Track all call sites via WeakReference set - All
CacheableCallSiteinstances are registered in a concurrent set using weak references, allowing targeted invalidation without preventing garbage collection.Add
clearCache()method to CacheableCallSite - When a metaclass changes, we can now clear the LRU cache and reset the fallback count on specific call sites rather than invalidating everything.Targeted invalidation on metaclass change -
invalidateSwitchPoints()now iterates through registered call sites, clearing caches and resetting targets as needed.Changes
CacheableCallSite.javaclearCache()method to clear LRU cache and reset fallback countIndyInterface.javaALL_CALL_SITESWeakReference set to track all call sitesregisterCallSite(CacheableCallSite)methodinvalidateSwitchPoints()to clear caches on all registered call sitesSelector.javaINDY_SWITCHPOINT_GUARDsystem property flag (default:false)JarJarTask.groovy(unrelated build fix)@InputFiles @Classpathto@InputonuntouchedFilesfield*were treated as literal file pathsConfiguration
The SwitchPoint guard behavior can be controlled via system property:
Benchmark Results
Tested using a dedicated benchmark suite measuring metaclass invalidation impact: https://github.com/jamesfredley/groovy-indy-performance
Complete Benchmark Comparison (3-Run Averages)
Test Date: February 4, 2026
Test Machine: Windows 11, 20 cores, 4GB max heap
Java Version: 17.0.18 (Amazon Corretto)
Versions Tested
Optimizations Applied in 6-snapshot-opt
INDY_SWITCHPOINT_GUARDdefaults tofalseWeakReferenceset🎯 KEY METRIC: Metaclass Invalidation Stress Test
This test measures the performance impact when metaclass changes occur during execution.
Lower ratio = better (less performance degradation from metaclass changes).
Key Finding
6-snapshot-opt reduces metaclass invalidation impact by:
Comprehensive Benchmark Suite (3-Run Averages)
Closure Benchmark Suite (3-Run Averages)
Loop Benchmark Suite (3-Run Averages)
Method Invocation Benchmark Suite (3-Run Averages)
Raw Data: Individual Run Results
Metaclass Invalidation Ratios
With Metaclass Changes (ms)
Baseline (No Metaclass Changes) (ms)
Existing Test Coverage:
Related