You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@grails.apache.org by "jamesfredley (via GitHub)" <gi...@apache.org> on 2026/02/03 16:54:35 UTC
[I] Grails 8: Invoke Dynamic (Indy) Optimization Opportunities [grails-core]
jamesfredley opened a new issue, #15374:
URL: https://github.com/apache/grails-core/issues/15374
### Issue description
Grails 7 exhibits a **~4x performance regression** compared to Grails 6 in GORM-based operations when compiled with Invoke Dynamic (Indy), compiling with indy off resolves this currently.
### Root Cause
Grails frameworks frequently modify metaclasses during request processing, GORM operations, and testing - triggering mass invalidation cascades that prevent JIT optimization.
---
## Problem Analysis
### How Groovy 4 Indy Works
1. **Bootstrap**: When a Groovy method is first called, `IndyInterface.bootstrap()` creates a `CacheableCallSite`
2. **Cache Lookup**: `fromCache()` checks if the method handle is already cached for the receiver's class
3. **Fallback**: If not cached, `selectMethod()` resolves the method and caches it
4. **Optimization**: After `INDY_OPTIMIZE_THRESHOLD` (10,000) hits, the call site target is updated to the cached method handle
**Problem**: When Grails modifies ANY metaclass (e.g., adding a method to a domain class), this invalidates ALL cached call sites in the entire application, forcing re-resolution.
## Recommended Improvements for Grails Framework
### 1. Framework-Level: Reduce Metaclass Modifications
**Goal**: Minimize the frequency of metaclass changes during request processing.
#### 1.1 Lazy/Deferred Metaclass Registration
Instead of modifying metaclasses during request processing, apply all metaclass modifications to application startup:
```groovy
// grails-core/src/main/groovy/grails/boot/GrailsApp.groovy
class GrailsApp extends SpringApplication {
@Override
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
super.afterRefresh(context, args)
// Trigger all lazy metaclass registrations BEFORE request processing begins
GrailsMetaClassRegistry.instance.finalizeMetaClasses()
}
}
```
#### 1.2 Batch Metaclass Modifications
When multiple metaclass changes are needed, batch them to trigger only ONE invalidation:
```groovy
// grails-core/src/main/groovy/org/grails/core/metaclass/MetaClassBatcher.groovy
class MetaClassBatcher {
private static final ThreadLocal<Boolean> batchMode = new ThreadLocal<>()
private static final ThreadLocal<List<Runnable>> pendingChanges = new ThreadLocal<>()
static void batch(Closure block) {
batchMode.set(true)
pendingChanges.set([])
try {
block()
// Apply all changes at once
pendingChanges.get().each { it.run() }
} finally {
batchMode.set(false)
pendingChanges.remove()
}
// Single invalidation for all batched changes
}
static void scheduleMetaClassChange(Runnable change) {
if (batchMode.get()) {
pendingChanges.get().add(change)
} else {
change.run()
}
}
}
```
### 2. GORM-Level: Optimize Dynamic Finders
**Goal**: Reduce metaclass modifications from GORM dynamic methods.
#### 2.1 Pre-Register Common Dynamic Finders at Startup
```groovy
// grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/GormEnhancer.groovy
class GormEnhancer {
void enhanceAll() {
// Register ALL potential dynamic finders at startup
domainClasses.each { domainClass ->
domainClass.persistentProperties.each { property ->
preRegisterDynamicFinder(domainClass, property)
}
}
}
private void preRegisterDynamicFinder(Class domainClass, PersistentProperty property) {
def metaClass = domainClass.metaClass
def propertyName = property.capitilizedName
// Pre-register findBy, findAllBy, countBy, etc.
['findBy', 'findAllBy', 'countBy', 'listOrderBy'].each { prefix ->
def methodName = "${prefix}${propertyName}"
if (!metaClass.respondsTo(domainClass, methodName)) {
metaClass."${methodName}" = { Object[] args ->
// Delegate to GORM
}
}
}
}
}
```
#### 2.2 Use Method Missing Cache
Instead of adding methods to metaclass dynamically, cache method resolutions:
```groovy
// Implement per-class method resolution cache that doesn't modify metaclass
class DynamicMethodCache {
private static final Map<String, Closure> methodCache = new ConcurrentHashMap<>()
static Closure getMethod(Class clazz, String methodName) {
String key = "${clazz.name}#${methodName}"
methodCache.computeIfAbsent(key) {
resolveDynamicMethod(clazz, methodName)
}
}
}
```
### 3. Code Generation: AST Transformations
**Goal**: Generate static method handles at compile time instead of runtime metaclass modifications.
#### 3.1 @StaticDynamicFinders AST Transform
```groovy
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@GroovyASTTransformationClass("org.grails.compiler.StaticDynamicFindersTransformation")
@interface StaticDynamicFinders {
}
// Generates static finder methods at compile time
@StaticDynamicFinders
class Book {
String title
String author
// AST generates:
// static Book findByTitle(String title) { ... }
// static Book findByAuthor(String author) { ... }
// static List<Book> findAllByTitleLike(String titlePattern) { ... }
}
```
#### 3.2 @PrecompiledGormMethods
```groovy
// grails-compiler/src/main/groovy/org/grails/compiler/PrecompiledGormMethodsTransformation.groovy
class PrecompiledGormMethodsTransformation extends AbstractASTTransformation {
void visit(ASTNode[] nodes, SourceUnit source) {
ClassNode classNode = nodes[1]
if (isDomainClass(classNode)) {
// Generate all GORM methods as static compiled methods
generateSaveMethod(classNode)
generateDeleteMethod(classNode)
generateGetMethod(classNode)
generateListMethod(classNode)
generateCountMethod(classNode)
// etc.
}
}
}
```
### 4. Configuration Options for End Users
#### 4.1 @CompileStatic for Hot Paths
Identify and annotate performance-critical code:
```groovy
// Services that handle many requests
@CompileStatic
class BookService {
Book findByIsbn(String isbn) {
Book.findByIsbn(isbn) // Compiles to static dispatch
}
List<Book> search(String query) {
// Static compilation avoids invokedynamic overhead
}
}
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [I] Grails 8 grails-core: Invoke Dynamic (Indy) Optimization Opportunities [grails-core]
Posted by "jamesfredley (via GitHub)" <gi...@apache.org>.
jamesfredley commented on issue #15374:
URL: https://github.com/apache/grails-core/issues/15374#issuecomment-3910274145
Changes listed under 1-3 did not have the expected positive impact.
```
┌───────────────────────────────────┬───────────┬───────────┬───────────────────┐
│ Optimization │ Run 2 Avg │ Run 3 Avg │ Assessment │
├───────────────────────────────────┼───────────┼───────────┼───────────────────┤
│ Baseline │ 314.06 ms │ 277.90 ms │ Reference │
├───────────────────────────────────┼───────────┼───────────┼───────────────────┤
│ 1.1 Lazy MetaClass Registration │ 297.10 ms │ 285.81 ms │ Marginal positive │
├───────────────────────────────────┼───────────┼───────────┼───────────────────┤
│ 1.2 Batch MetaClass Modifications │ 321.41 ms │ 282.14 ms │ Neutral │
├───────────────────────────────────┼───────────┼───────────┼───────────────────┤
│ 2.1 Pre-Register Dynamic Finders │ 299.06 ms │ 291.34 ms │ Marginal positive │
├───────────────────────────────────┼───────────┼───────────┼───────────────────┤
│ 2.2 ConcurrentHashMap Cache │ 332.68 ms │ 364.19 ms │ NEGATIVE │
├───────────────────────────────────┼───────────┼───────────┼───────────────────┤
│ 3.1 @StaticDynamicFinders AST │ 325.84 ms │ 295.41 ms │ Neutral │
├───────────────────────────────────┼───────────┼───────────┼───────────────────┤
│ 3.2 @PrecompiledGormMethods AST │ 302.50 ms │ 301.19 ms │ Neutral │
└───────────────────────────────────┴───────────┴───────────┴───────────────────┘
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [I] Grails 8: Invoke Dynamic (Indy) Optimization Opportunities [grails-core]
Posted by "jamesfredley (via GitHub)" <gi...@apache.org>.
jamesfredley commented on issue #15374:
URL: https://github.com/apache/grails-core/issues/15374#issuecomment-3842905327
https://github.com/apache/grails-core/issues/15293 has ended up focused on changes in groovy vs changes in grails and I think having this separate will allow focus on Grails optimizations.
I also think the timelines will be distinct, Groovy 4 potential changes could apply to Grails 7.x and this list above will need to wait for Grails 8.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [I] Grails 8 grails-core: Invoke Dynamic (Indy) Optimization Opportunities [grails-core]
Posted by "jamesfredley (via GitHub)" <gi...@apache.org>.
jamesfredley commented on issue #15374:
URL: https://github.com/apache/grails-core/issues/15374#issuecomment-5123391461
1-3 were not implemented.
https://github.com/apache/grails-core/pull/15800 and https://github.com/apache/grails-core/pull/15828 are linked to this issue and once those are completed with close this issue. All have been moved to 8.0.0-RC2
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [I] Grails 8: Invoke Dynamic (Indy) Optimization Opportunities [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on issue #15374:
URL: https://github.com/apache/grails-core/issues/15374#issuecomment-3842780366
Should this go under https://github.com/apache/grails-core/issues/15293 intead of creating another ticket so we can track the history here?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [I] Grails 8 grails-core: Invoke Dynamic (Indy) Optimization Opportunities [grails-core]
Posted by "jamesfredley (via GitHub)" <gi...@apache.org>.
jamesfredley closed issue #15374: Grails 8 grails-core: Invoke Dynamic (Indy) Optimization Opportunities
URL: https://github.com/apache/grails-core/issues/15374
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org