Hi Jochen,

Response below.

Cheers, Paul.

--------

> This means asType adaption and explicitCastArguments does not cost? Also
> interesting would be the cost of inserting and dropping arguments in
> terms of Lambda forms

Measured (pure java.lang.invoke, no Groovy, fresh JVM per mode, count of
spun LambdaForm$* classes; 8 variations per mode, minus a raw-handle
control where applicable):

  asType / explicitCastArguments (ref or prim)   +0 over the raw handles
  insertArguments x8 (same shape)                +7 total (one-time, then free)
  guardWithTest x8 / catchException x8           +5 / +6 (same pattern)
  dropArguments, arities 1-8                     +26 (~3.2 per resulting shape)
  asCollector / asSpreader, arities 1-8          +34 / +24 (~3-4 per arity)
  8 raw handles, distinct primitive signatures   +36 (~4.5 per basic-type shape)
  8 raw handles, distinct reference signatures   +8 (all erase to L)

So yes — asType/explicitCastArguments literally cost nothing by
themselves. The law that explains all of it (and the earlier table you
weren't sure how to read): LambdaForm cost tracks DISTINCT BASIC-TYPE
SHAPES touched, not combinators and not call sites. Reference types all
erase to basic type L, so reference diversity is free; primitives and
arity are the basic-type dimensions, so they are the only real
multipliers. Your mismatch theory was right in effect but the mechanism
is shape-counting: the adaptation step is innocent, the primitive shapes
it connects are the cost. In our full chains one site-shape drags ~13-17
LFs because bootstrap collector + guards + spreader each derive shapes
from it.

Earlier-table reading guide: each row is one generated workload varying
one dimension, run in a fresh JVM; the number is loaded LambdaForm
classes minus the 142 a bare Groovy runtime loads. "32 sites vs 1 site:
+29 vs +29" means sites are free at constant shape.

> still possible that creating those hidden classes costs too much though.
> Also we need an efficient lookup for those [...]

The GROOVY-12137 spike suggests the template class may be optional
rather than required: plain Method.invoke (which shares the JDK's own
per-arity infrastructure post-JEP-416) already removes ~2/3 of the
per-shape LF cost and gives 1.34-1.40x faster cold dispatch on
many-cold-sites workloads, with zero per-method class creation and no
lookup structure at all. Promotion bounds reflective residence to at
most threshold calls per receiver shape, after which the hot path is
byte-for-byte what we have today. So the hidden-class template becomes a
targeted optimisation of cold per-call overhead, to be justified by
measurement, not a prerequisite — and if we do build it, hanging the
cache off CachedMethod (like the old callsite-constructor cache, so
classloader lifecycle comes for free) seems the natural lookup.

On Tue, Jul 7, 2026 at 9:48 PM Jochen Theodorou <[email protected]> wrote:
>
> On 7/7/26 11:48, Paul King wrote:
> [...]
> >> My idea would be to have a baseline benchmark in Groovy unrelated code
> >> in pure Java. And then adjust everything to be relative to that.
> >
> > Your mail crossed the implementation :-) That's exactly what landed as
> > GROOVY-12127 on July 5: pure-Java "calibration ruler" benchmarks (integer
> > throughput, pointer-chase, allocation churn) now run inside every CI suite
> > shard, and the per-PR summary comment shows a hardware-calibrated speedup
> > column next to the raw one, plus a warning when the runner deviates >15% 
> > from
> > the baseline hardware. Your caveat — that a runner isn't uniform *within* a
> > run either — is fair; the planned refinement is a second ruler that sorts to
> > the end of each suite's execution so we can report intra-run drift too.
>
> nice
>
> [...]> Small generated workloads, each varying ONE dimension, delta
> LambdaForms vs a
> > 142-LF baseline JVM (deterministic, reproducible):
> >
> >    arity diversity (arities 1-8, all-Object args)     +136   (~17 LF/arity)
> >    primitive arg diversity (8 primitive shapes)       +110   (~13 LF/shape)
> >    one (R,int)Object call site (control)               +29
> >    primitive RETURN diversity (vs uniform int +14)     +27    (~2 LF each)
> >    8 sites, all-Object args (control)                  +17
> >    reference arg diversity (8 distinct classes)        +16    (~free)
> >    reference return diversity                          +12    (~free)
> >    32 sites vs 1 site, identical shape+target        +29 vs +29  (0 per 
> > site)
>
> interesting, though not sure I interpret the numbers correctly
>
> > Conclusions:
> >
> > 1. My W2 proposal (erase reference types in indy descriptors) is dead — the
> >     JVM already shares LambdaForms across reference-typed shapes, so erasure
> >     would buy roughly nothing. Your skepticism was correct; no compiler 
> > change
> >     should be written for that.
> >
> > 2. Your mismatch theory is confirmed, with a refinement: the 
> > callsite<->target
> >     adaptation cost is real but specifically for the PRIMITIVE dimensions of
> >     MethodType (boxing adaptations and per-arity forms). Reference-type
> >     mismatches fold into shared forms for free. LambdaForms are shared 
> > across
> >     call sites of identical shape — the cost is per SHAPE, not per site.
>
> per shape was clear to me. The Reference-type mismatch being essentially
> free but not primitives is kind of surprising. I mean it is clear to me
> that the primitives do cost, but reference types being free not so much.
> This means asType adaption and explicitCastArguments does not cost? Also
> interesting would be the cost of inserting and dropping arguments in
> terms of Lambda forms
>
> > 3. This strengthens the cold-tier direction you're already circling (the
> >     reflection/MOP-dispatching cold path, your hidden-class template idea): 
> > an
> >     (Object, Object[])Object cold dispatcher erases both expensive 
> > dimensions
> >     at once — one shape for every arity and every primitive pattern — which 
> > is
> >     precisely why reflection is cold-fast. The specialised primitive shapes
> >     then get paid lazily, only by the sites that earn promotion to hot.
>
> still possible that creating those hidden classes costs too much though.
> Also we need an efficient lookup for those, since we would have one per
> "called" method in the receiver globally... well per module and
> classloader would also work for JPMS compatibility.
>
> bye Jochen
>

Reply via email to