Hi Jochen,

https://github.com/apache/groovy/pull/2692 (closure packing) was a WIP
and should have been marked draft. The reflective code is now gone and
GEP-27 is up-to-date wrt the current PR.

Below is a quick summary of where it stands now, compared against the
current behaviour (one generated Closure class per closure literal).
As a heads-up, the summary mentions some Grails-like classes I used
for testing.

TLDR version: There are big footprint/memory gains but a performance
penalty at the moment - but it is opt-in, so that is user's choice.
More optimizations are possible for spread/multi-arity cases but they
would make the PR harder to review. I'd like some of those to be done
for Groovy 6, but some are good candidates to defer to Groovy 7 too.

Cheers, Paul.

-------

FOOTPRINT / MEMORY - the win
----------------------------
- One generated class per closure literal is removed: fewer classes to load,
  verify, JIT-profile, and hold in metaspace.
- ~90% of a closure class is Closure scaffolding (constructor, metaclass init,
  serialVersionUID, Reference fields), so bytecode shrinks sharply:
    * FormTagLib sample:      8 class files -> 1;  23.8 KB -> 7.3 KB (~69% less)
    * RemarkCriteria domain:  24 closure classes / 80 KB removed
- The nested-closure name explosion (Outer$_m_closure1$_closure2$_closure3) is
  gone for packed closures.
- Heap: a packed closure allocates one instance per literal-evaluation, same as
  before; captured values live in the MethodHandle, so there is NO per-call
  array allocation even for capturing closures. Per-call heap is on par with a
  normal closure - the memory saving is metaspace, not per-call heap.


DISPATCH THROUGHPUT - x a normal closure, lower is better
---------------------------------------------------------
  Closure shape                    @CompileStatic       dynamic (opt-in)
  -----------------------------    -----------------    ----------------
  capturing    { it + base }       0.78x  (faster)      ~1.9x
  non-capturing{ it * 2 }          ~parity (0.7-1.2x)   ~2.0x


BOTTOM LINE
-----------
- @CompileStatic packing is a strict win: smaller AND at-or-above dispatch
  parity, with no per-call allocation penalty.
- Dynamic packing (opt-in) is a size-for-speed trade: smaller, but ~2x on
  dispatch. The residual is the argument-array spreader plus the dynamic MOP
  call->doCall selection - not allocation - and is the piece a follow-up
  per-arity invokeExact path would address.

On Sun, Jul 12, 2026 at 2:28 PM Jochen Theodorou <[email protected]> wrote:
>
> Hi all,
>
>
> there are some thoughts about GEP-27 I want to share.
>
> First of all, compiling lambdas without going through a Closure is a
> good thing. And I think that should always happen, in static and dynamic
> code. I do not think that is the implementation in current Groovy 6.
> What do we actually do if defaults are used for the lambda parameters? I
> ahve the feeling we may handle this wrong right now.
>
> Closures are more difficult because of the mutable state they carry and
> because they are initially planed as inner class construct.
>
> PackedClosure as in https://github.com/apache/groovy/pull/2692
>
> Loss of GeneratedClosure.
> This is not a marker interface for that we have a Closure generated by
> the compiler as primary goal. The primary goal is to use this
> information, that this is a controlled class without possible
> shenanigans from the user to allow optimizations. Those are lost now. I
> already commented on the huge amount of basically reflective code in
> there. There is an abstraction we have for this and that would be
> MethodHandles. I think It should get a MethodHandle as parameter, with
> the handle beeing a constant in the hosting class and invoke it.
> And again here I have to ask how defaults are handled? From the code I
> see, not at all. The default variant is simply ignored. Of course a
> single handle would then also not work. I am putting it here because I
> think the default arguments problem must be discussed.
>
> Then there is a lot of talk in the GEP and the PR about @DelegatesTo
> indicating a delegation usage. Well, yes, CS can use that, but it really
> is not enforcing. @DelegatesTo -> setDelegate most likely used, No
> @DelegateTo -> setDelegate might still be used.
>
>
> Which leads me to another point. MethodClosure.
> In theory MethodClosure is in a similar situation. You cannot
> effectively set the delegate. ClosureStrategy does not really work. But
> we have a break in style here. MethodClosure is silently ignoring.
> PackedClosure not. In theory MethodClosure and PackedClosure are the
> same beasts, only that in case of PackedClosure we actually know the
> target method(s) even in dynamic mode fully.
>
> Just to make this clear. I think MethodClosure also needs a rework. But
> then we should consider aligning PackedClosure and MethodClosure.
>
> What do you think?
>
> bye Jochen
  • GEP-27 S1 Jochen Theodorou
    • Re: GEP-27 S1 Paul King

Reply via email to