[
https://issues.apache.org/jira/browse/GROOVY-11726?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18138779#comment-18138779
]
ASF GitHub Bot commented on GROOVY-11726:
-----------------------------------------
daniellansun opened a new pull request, #3228:
URL: https://github.com/apache/groovy/pull/3228
https://issues.apache.org/jira/browse/GROOVY-11726 reports that
`GroovyClassLoader.parseClass(GroovyCodeSource, boolean)` roughly doubled in
wall time for the same script batch between Groovy 3.0.13 and 4.0.24.
## Measurement
JDK 21.0.12. Representative dynamic scripts (properties, closures, GStrings,
maps), unique sources so the source cache cannot skip compilation.
Sequential `parseClass` did not show a 2× gap:
| Runtime | Batch | Wall time |
|---|---|---|
| Groovy 3.0.13 | 600 scripts, 8 methods | 3527 ms |
| Groovy 4.0.24 | same | 3779 ms |
| Groovy 3.0.13, `-Dgroovy.antlr4.cache.threshold=-1` | same | 3090 ms |
| Groovy 4.0.24, same flag | same | 3360 ms |
Indy on or off, an 80-jar classpath, and those jars on the parent loader
stayed within a few percent. JFR on both release jars shows the same hot
methods: lexer ATN simulation, then `ClassNode` work and class generation.
Groovy 3.0.13's ASM cannot read JDK 21 class files, so `@CompileStatic` against
JDK classes was not compared. On this master, one thread compiling 80 heavier
scripts took 967 ms, against 1077 ms for Groovy 4.0.24 on the same harness.
## What the batch was waiting on
`parseClass` compiled inside `FlexibleCache.getAndPut`. That method holds
its write lock for the whole value-provider call, so every compilation on one
`GroovyClassLoader` waited for every other one, including distinct sources. On
Groovy 4.0.24 a 4-thread batch of 80 scripts took 1101 ms, against 1077 ms on
one thread.
## Change
Compile outside that lock. After a cache miss, `doParseClass` runs, then
`getAndPut` only publishes the class already built; the first publisher for a
key is kept. `definePackage` tolerates a concurrent definition of the same
package.
`bugs.Groovy11726` checks a cache hit, an uncached recompile, and that four
threads overlap inside conversion. Before this change the overlap assertion
failed with "overlapped 1 at a time".
## After
Same 80-script harness on this commit: 967 ms on one thread, 690 ms and 680
ms on four threads. Shared parser DFA state still limits scaling, so four
threads are not four times faster.
## Residual risk
Two threads that miss the cache for the same source can both compile; only
one class is stored. A cycle in which script A loads script B while B loads A
is no longer serialized by the global lock. Independent batches are the case
this changes. `IllegalArgumentException` from `definePackage` is ignored only
when that package is already defined.
`./gradlew :test --tests bugs.Groovy11726 --tests
groovy.lang.GroovyClassLoaderTest` and `./gradlew lintGate` passed locally.
### Testing
- `bugs.Groovy11726`
> Performance Regression in GroovyClassLoader.parseClass After Migration to
> Groovy 4
> ----------------------------------------------------------------------------------
>
> Key: GROOVY-11726
> URL: https://issues.apache.org/jira/browse/GROOVY-11726
> Project: Groovy
> Issue Type: Bug
> Reporter: Puneet Singla
> Priority: Critical
> Labels: performance
>
> After upgrading to Groovy 4, we have observed a *significant increase in
> compilation time* when using the following method from {{GroovyClassLoader}}:
> {code:java}
> public Class parseClass(final GroovyCodeSource codeSource, boolean
> shouldCacheSource) throws CompilationFailedException
> {code}
> This function was previously performing optimally in Groovy 3.0.13, but with
> Groovy 4.0.24, compilation latency has *increased significantly.* Earlier it
> used to take about 4 minutes to compile a bunch of scripts but now it is
> taking about 9 minutes for the same scripts.
> ----
> *Steps to Reproduce:*
> # Use {{GroovyClassLoader}} to compile Groovy scripts at runtime using the
> {{parseClass(GroovyCodeSource, boolean)}} method.
> # Benchmark the time taken before and after the migration to Groovy 4.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)