GROOVY-12137: experimental reflective cold tier for indy dispatch (sp…#2673
Open
paulk-asert wants to merge 1 commit into
Open
GROOVY-12137: experimental reflective cold tier for indy dispatch (sp…#2673paulk-asert wants to merge 1 commit into
paulk-asert wants to merge 1 commit into
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #2673 +/- ##
==================================================
- Coverage 68.7019% 68.6328% -0.0691%
+ Complexity 34123 34119 -4
==================================================
Files 1536 1537 +1
Lines 128912 129033 +121
Branches 23369 23406 +37
==================================================
- Hits 88565 88559 -6
- Misses 32517 32639 +122
- Partials 7830 7835 +5
🚀 New features to boost your workflow:
|
blackdrag
reviewed
Jul 7, 2026
e1d35a3 to
fec99f7
Compare
Contributor
Author
|
@blackdrag updated - please reassess |
blackdrag
reviewed
Jul 8, 2026
…ike)
Behind the groovy.indy.cold.reflection flag (default: off), plain method
calls dispatch reflectively while a call site is cold, deferring all
MethodHandle chain construction — and its one-time LambdaForm creation
cost — to hit-count promotion. The shared cold dispatcher has a single
(Object[])Object shape for every call site, arity, and primitive
pattern; promotion installs the unchanged full guarded chain, so the
hot path is identical to master.
Plain calls are public, non-static, non-category CachedMethods on
public classes with a real receiver and no spread/safe-null/interceptor
semantics; caller-sensitive targets (including unannotated
caller-context-sensitive cases like object serialization, and interface
selections resolving to sensitive implementations) stay on the full
path. Selection reuses Selector's existing logic via a new
selection-only hook; validity is re-checked per call with plain-Java
equivalents of the chain's guards. A per-wrapper cumulative counter
promotes polymorphic receiver shapes that the consecutive-hit counter
never catches.
Measured (deterministic class-load counters + fresh-JVM timing):
~2/3 of per-shape LambdaForm cost removed on cold dispatch workloads,
1.34-1.40x faster cold dispatch with many cold sites, steady state
unchanged after promotion, MOP-heavy test subset (3437 tests) green
with the flag on.
Changes from review feedback: consolidate caller-sensitivity, harden invariants
- selectForColdReflection now throws GroovyBugError for non-METHOD call
types instead of silently returning null, so a broken fallback gate
cannot hide behind the full handle path
- the NullObject exclusion is marked TODO: its methods are ordinary
methods on a singleton receiver and could be served reflectively
- all method-determined caller-sensitivity — the annotation probe plus
declaring-class cases like object serialization's stack-walking loader
lookup — now lives behind CachedMethod#isCallerSensitive, computed
once per method; the wrapper keeps only the receiver-dependent case
- implementation resolution now applies to abstract selections only
(e.g. the metaclass picking ObjectInput.readObject() for a call on an
ObjectInputStream): interface default methods carry their own
annotations, are probed directly, and take the reflective tier
✅ All tests passed ✅🏷️ Commit: 750878e Learn more about TestLens at testlens.app. |
Contributor
Author
|
@blackdrag Updated - ready for another assessment if you have time. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…ike)
Behind the groovy.indy.cold.reflection flag (default: off), plain method calls dispatch reflectively while a call site is cold, deferring all MethodHandle chain construction — and its one-time LambdaForm creation cost — to hit-count promotion. The shared cold dispatcher has a single (Object[])Object shape for every call site, arity, and primitive pattern; promotion installs the unchanged full guarded chain, so the hot path is identical to master.
Plain calls are public, non-static, non-category CachedMethods on public classes with a real receiver and no spread/safe-null/interceptor semantics; caller-sensitive targets (including unannotated caller-context-sensitive cases like object serialization, and interface selections resolving to sensitive implementations) stay on the full path. Selection reuses Selector's existing logic via a new selection-only hook; validity is re-checked per call with plain-Java equivalents of the chain's guards. A per-wrapper cumulative counter promotes polymorphic receiver shapes that the consecutive-hit counter never catches.
Measured (deterministic class-load counters + fresh-JVM timing): ~2/3 of per-shape LambdaForm cost removed on cold dispatch workloads, 1.34-1.40x faster cold dispatch with many cold sites, steady state unchanged after promotion, MOP-heavy test subset (3437 tests) green with the flag on.