[
https://issues.apache.org/jira/browse/GROOVY-12381?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Paul King updated GROOVY-12381:
-------------------------------
Description:
Follow-up 2 from GROOVY-12365: a measurement pass over OpenJDK's Leyden work
(JEP 483 ahead-of-time class loading and linking, JEP 514 AOT cache ergonomics,
JEP 515 method profiling) as shipped in JDK 25, to see what it gives a
*dynamic* Groovy program and where Groovy's own dispatch stands in the way. No
code change; this issue records the numbers and the conclusions so the
discussion has a baseline.
h3. Setup
* The native-image probe from {{subprojects/tests-native}} (dynamic Groovy:
closures, traits, ranges, GStrings, metaclass access, async scope, NIO, XML and
JSON), compiled once and jarred. The AOT cache requires jar entries on the
class path; a populated class directory fails with "Cannot have non-empty
directory in paths".
* Zulu 25.0.4 and Amazon Corretto 26.0.1, macOS aarch64. Training and cache
creation in one step: {{java -XX:AOTCacheOutput=probe.aot -XX:+AOTClassLinking
-cp ... probe}}; use: {{java -XX:AOTCache=probe.aot -XX:+AOTClassLinking ...}}.
Twelve interleaved runs per mode; class-load sources from {{-Xlog:class+load}};
Groovy link events from {{-Dgroovy.indy.logging=true}}; archived
{{invokedynamic}} entries from {{-Xlog:aot+resolve=trace}} at training.
h3. Results (JDK 25; JDK 26 identical within noise)
||measure||no cache||AOT cache||
|startup + run, median|409 ms|236 ms|
|classes loaded from the cache|1256 (default CDS archive)|2973 of 3261|
|classes loaded from jars|668|8|
|hidden classes defined at run time|216|1|
|LambdaForms spun at run time|293|262|
|Groovy indy link events|125|125|
* Cache size about 30 MB. Seven Groovy classes are skipped as "Unlinked class
not supported by AOTClassLinking": the compiler's ASM
{{ClassWriter}}/{{ClassRemapper}} family, which the probe never links.
* The training log reports every one of the 275 {{invokedynamic}} constant-pool
entries archived. All of them are {{LambdaMetafactory}} or
{{StringConcatFactory}} sites: 103 in the JDK and 172 inside javac-compiled
Groovy runtime classes ({{NumberMathModificationInfo}} 78, {{Java9}} 14,
{{Closure}} 14, {{MetaClassImpl}} 6, {{MetaClassRegistryImpl}} 5). The probe
class has 94 {{IndyInterface}}-bootstrapped sites; none is archived. As
expected, JDK 25 archives its own bootstraps only.
* The reflective cold tier ({{-Dgroovy.indy.cold.reflection=true}}, cache
trained with it on) changes neither the times nor the number of spun
LambdaForms.
h3. Conclusions
# For users the gain is real and free: about 40% off startup of a dynamic
program from class loading and linking alone, with no Groovy change. Worth a
paragraph in the documentation next to the native-image section, including the
jar-only class-path rule.
# The residual cost is Groovy's call-site linking: the link count is unchanged
and roughly 260 LambdaForms are still spun on every start. Two independent
obstacles keep the cache from removing it:
#* JDK 25 does not archive {{invokedynamic}} sites for non-JDK bootstrap
methods at all.
#* Even if it did, {{IndyInterface.bootstrap}} returns a {{MutableCallSite}}
with a fallback target; method selection and guarded-chain construction happen
on first invocation and hold {{SwitchPoint}} and metaclass state, none of which
is archivable. An archived bootstrap result would still relink per start.
# A Groovy-side answer would need selection results that survive archiving: the
AOT link mode's idea of a permanently linked site, built during a training run
from handle graphs the cache can hold. That is a dispatch-design question
(GEP-31 territory), not a fix for 6.0.
h3. Next steps
* Repeat the same script on the Leyden early-access build (premain branch,
jdk.java.net/leyden), which adds AOT-compiled code and has been widening what
{{invokedynamic}} sites are archived; the question is whether the residual 262
LambdaForms and 125 links shrink there.
* Document the JDK 25 recipe and numbers for users.
* Keep the measurement script with the native probe so it can be rerun as JDKs
move; the probe already exists for exactly this purpose.
> Leyden: measure JDK 25 AOT cache on dynamic Groovy; indy sites relink per
> start
> -------------------------------------------------------------------------------
>
> Key: GROOVY-12381
> URL: https://issues.apache.org/jira/browse/GROOVY-12381
> Project: Groovy
> Issue Type: Task
> Reporter: Paul King
> Assignee: Paul King
> Priority: Major
>
> Follow-up 2 from GROOVY-12365: a measurement pass over OpenJDK's Leyden work
> (JEP 483 ahead-of-time class loading and linking, JEP 514 AOT cache
> ergonomics, JEP 515 method profiling) as shipped in JDK 25, to see what it
> gives a *dynamic* Groovy program and where Groovy's own dispatch stands in
> the way. No code change; this issue records the numbers and the conclusions
> so the discussion has a baseline.
> h3. Setup
> * The native-image probe from {{subprojects/tests-native}} (dynamic Groovy:
> closures, traits, ranges, GStrings, metaclass access, async scope, NIO, XML
> and JSON), compiled once and jarred. The AOT cache requires jar entries on
> the class path; a populated class directory fails with "Cannot have non-empty
> directory in paths".
> * Zulu 25.0.4 and Amazon Corretto 26.0.1, macOS aarch64. Training and cache
> creation in one step: {{java -XX:AOTCacheOutput=probe.aot
> -XX:+AOTClassLinking -cp ... probe}}; use: {{java -XX:AOTCache=probe.aot
> -XX:+AOTClassLinking ...}}. Twelve interleaved runs per mode; class-load
> sources from {{-Xlog:class+load}}; Groovy link events from
> {{-Dgroovy.indy.logging=true}}; archived {{invokedynamic}} entries from
> {{-Xlog:aot+resolve=trace}} at training.
> h3. Results (JDK 25; JDK 26 identical within noise)
> ||measure||no cache||AOT cache||
> |startup + run, median|409 ms|236 ms|
> |classes loaded from the cache|1256 (default CDS archive)|2973 of 3261|
> |classes loaded from jars|668|8|
> |hidden classes defined at run time|216|1|
> |LambdaForms spun at run time|293|262|
> |Groovy indy link events|125|125|
> * Cache size about 30 MB. Seven Groovy classes are skipped as "Unlinked class
> not supported by AOTClassLinking": the compiler's ASM
> {{ClassWriter}}/{{ClassRemapper}} family, which the probe never links.
> * The training log reports every one of the 275 {{invokedynamic}}
> constant-pool entries archived. All of them are {{LambdaMetafactory}} or
> {{StringConcatFactory}} sites: 103 in the JDK and 172 inside javac-compiled
> Groovy runtime classes ({{NumberMathModificationInfo}} 78, {{Java9}} 14,
> {{Closure}} 14, {{MetaClassImpl}} 6, {{MetaClassRegistryImpl}} 5). The probe
> class has 94 {{IndyInterface}}-bootstrapped sites; none is archived. As
> expected, JDK 25 archives its own bootstraps only.
> * The reflective cold tier ({{-Dgroovy.indy.cold.reflection=true}}, cache
> trained with it on) changes neither the times nor the number of spun
> LambdaForms.
> h3. Conclusions
> # For users the gain is real and free: about 40% off startup of a dynamic
> program from class loading and linking alone, with no Groovy change. Worth a
> paragraph in the documentation next to the native-image section, including
> the jar-only class-path rule.
> # The residual cost is Groovy's call-site linking: the link count is
> unchanged and roughly 260 LambdaForms are still spun on every start. Two
> independent obstacles keep the cache from removing it:
> #* JDK 25 does not archive {{invokedynamic}} sites for non-JDK bootstrap
> methods at all.
> #* Even if it did, {{IndyInterface.bootstrap}} returns a {{MutableCallSite}}
> with a fallback target; method selection and guarded-chain construction
> happen on first invocation and hold {{SwitchPoint}} and metaclass state, none
> of which is archivable. An archived bootstrap result would still relink per
> start.
> # A Groovy-side answer would need selection results that survive archiving:
> the AOT link mode's idea of a permanently linked site, built during a
> training run from handle graphs the cache can hold. That is a dispatch-design
> question (GEP-31 territory), not a fix for 6.0.
> h3. Next steps
> * Repeat the same script on the Leyden early-access build (premain branch,
> jdk.java.net/leyden), which adds AOT-compiled code and has been widening what
> {{invokedynamic}} sites are archived; the question is whether the residual
> 262 LambdaForms and 125 links shrink there.
> * Document the JDK 25 recipe and numbers for users.
> * Keep the measurement script with the native probe so it can be rerun as
> JDKs move; the probe already exists for exactly this purpose.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)