Re: Groovy > 3 performance

Jochen Theodorou <[email protected]>
Newsgroups gmane.comp.lang.groovy.devel
Message-ID <[email protected]>
On 14.01.26 12:27, Gianluca Sartori wrote:
[...]
> I've asked ChatGPT, how much of this is true? How can we move forward 
> thinking about performance as something important to consider for the 
> language?
> 
> *Q: *Why is Groovy 5 slower than Groovy 3?
> 
> *A:*
> 
> The reason isn’t a single bug, but rather a combination of architectural 
> and implementation changes. Let me break it down carefully.
> 
> ------------------------------------------------------------------------
> 
> 
>       1. *AST Transformations and Compilation Changes*
> 
> Groovy 5 introduced a *new compilation infrastructure* that relies more 
> heavily on *AST (Abstract Syntax Tree) transformations*. While this 
> makes the language more flexible and enables new features (like better 
> type checking and improved static compilation), it also *adds overhead 
> at runtime*, especially for dynamic code.
>   *
> 
>     In Groovy 3, some dynamic method calls were optimized in a simpler way.
> 
>   *
> 
>     Groovy 5 performs *additional type inference and checking*, even at
>     runtime for some constructs, which can slow execution.

The Groovy 5 point is nonsense. Was it a more simple way in Groovy 3? 
Maybe, but Java supports it less and less. Actually, what we removed is 
the alternative bytecode path for unchanged meta classes. This path did 
help a lot when it comes to handle primitives and tight loops using 
primitives. But the removal is more a simplification and also means not 
to have the overhead for this as well.

> ------------------------------------------------------------------------
> 
> 
>       2. *JVM Dynamic Call Optimizations*
> 
> Groovy 5 changed its *call site caching mechanism*:
> 
>   *
> 
>     Groovy 3 used *classic call site caching* that was very fast for
>     repeated method calls.
> 
>   *
> 
>     Groovy 5 introduced a *more sophisticated but heavier mechanism* to
>     improve correctness with new language features (like traits and
>     records).
> 
>   *
> 
>     This improves *compatibility and correctness*, but can be slower for
>     tight loops with many dynamic calls.

The point about traits and records is nonsense. Groovy 5 is still very 
fast, if not faster for repeated method calls. Actually Groovy 5 may be 
even better in optimization in tight loops. The problem is the 
initialization is too expensive.

> ------------------------------------------------------------------------
> 
> 
>       3. *Library and API Changes*
> 
> Groovy 5 upgraded many core libraries and dependencies, which sometimes 
> means:
> 
>   *
> 
>     Using newer, safer APIs that are a bit slower.
> 
>   *
> 
>     Removing some low-level optimizations present in Groovy 3 for
>     backward compatibility reasons.

this is pure speculation? What library or dependency did we change that 
changes runtime performance? None, moving away from bytecode generation 
to invokedynamic maybe.

> ------------------------------------------------------------------------
> 
> 
>       4. *Static Compilation Differences*
> 
> If you used |@CompileStatic| in Groovy 3:
> 
>   *
> 
>     The bytecode generated in Groovy 5 can sometimes be *larger and less
>     optimized*, because Groovy 5 focuses on *full feature support*,
>     sometimes at the expense of raw performance.
> 
>   *
> 
>     Groovy 5’s static compilation is closer to Java semantics, which
>     adds minor overhead in certain dynamic-heavy scenarios.

No idea what that AI is talking about here.

> ------------------------------------------------------------------------
> 
> 
>       5. *Benchmarks and Real-World Performance*
> 
>   *
> 
>     For purely *dynamic Groovy scripts*, Groovy 5 tends to be slower than 3.
> 
>   *
> 
>     For *statically compiled code*, the difference is smaller, but
>     sometimes Groovy 5 can even be slightly slower due to the AST overhead.
> 
>   *
> 
>     Microbenchmarks in loops and repeated method calls show *Groovy 5
>     can be 20–50% slower* in some cases.

AST overhead plays only a role if you add the compilation into the 
microbenchmark. Then it is not so micro anymore imho. And it is a one 
time cost. Groovy is no AST interpreter after all. As for the other 
statement... I think I know worse cases. The question is how common they 
are.

> ------------------------------------------------------------------------
> 
> ✅ *In short:*
> Groovy 5 trades raw runtime speed for *better correctness, new language 
> features, and more powerful static compilation*. For heavy dynamic 
> Groovy code, this can feel noticeably slower than Groovy 3.

I too noticed that chatgpt is quite bad when it comes to Groovy. Is 
there so much misinformation out there?

>     1️⃣ JVM Flags to Monitor |invokedynamic|
> 
> To count and monitor invokedynamic usage:
> 
> |-XX:+PrintCallSiteCounters -XX:+UnlockDiagnosticVMOptions 
> -XX:+LogCompilation -XX:+PrintInlining |
> 
>   *
> 
>     |-XX:+PrintCallSiteCounters| → shows how many times each call site
>     was created or invoked
> 
>   *
> 
>     |-XX:+LogCompilation| + |-XX:+PrintInlining| → lets you see which
>     calls the JIT inlines
> 
> When running Groovy, pass them to the JVM with |-J|:
> 
> |groovy -J-XX:+PrintCallSiteCounters myscript.groovy |

not sure about PriintCallSiteCounters, since I do not know how indy is 
handled by this. But ok.

> ------------------------------------------------------------------------
> 
> 
>     2️⃣ Test Script
> 
> Here’s a small script that tends to slow down in Groovy 4:
> 
> |// test.groovy 10_000.times { i -> def a = "string" def b = a + i 
> println b } |

sarcasm mode on... oh, println in a tight loop... surely that will not 
have any impact what so ever... sarcasm mode off. Well I guess it is not 
about time

>   *
> 
>     Uses *dynamic operators* (|+|)
> 
>   *
> 
>     Uses *variable types*
> 
>   *
> 
>     Calls |println| dynamically
> 
> This simulates a *hot loop* with many dynamic call sites.
> 
> ------------------------------------------------------------------------
> 
> 
>     3️⃣ Run with Groovy 3 vs Groovy 4
> 
> |# Groovy 3 groovy -J-XX:+PrintCallSiteCounters test.groovy # Groovy 4 
> groovy -J-XX:+PrintCallSiteCounters test.groovy |
> 
> ------------------------------------------------------------------------
> 
> 
>     4️⃣ Sample Output
> 
> |CallSite counters: invokedynamic site #1: 10000 invocations, 0 
> invalidations invokedynamic site #2: 10000 invocations, 50 invalidations 
> invokedynamic site #3: 10000 invocations, 200 invalidations |
> 
>   *
> 
>     *invocations* → how many times the call site was called
> 
>   *
> 
>     *invalidations* → how many times the call site had to be recreated
> 
> *Analysis*:
> 
>   *
> 
>     Groovy 3: few call sites, few invalidations → loop runs fast
> 
>   *
> 
>     Groovy 4: more dynamic call sites + more invalidations → overhead
>     increases → slower performance

We need specific output to talk about this. I know only that 
PrintCallSiteCounters does show more than just invokedynamic sites. 
invalidations is interesting, but as I said, this would have to be 
discussed with specific output.

[...]
> ------------------------------------------------------------------------
> 
> 
>     ✅ Key Takeaways
> 
>  1.
> 
>     |invokedynamic| itself is *not slow*
> 
>  2.
> 
>     Groovy 4 is slower than Groovy 3 because:
> 
>       *
> 
>         it uses *more invokedynamic call sites*
> 
>       *
> 
>         more *call site invalidations*
> 
>       *
> 
>         the *MOP* is more dynamic and general

scratch that last point imho.


[...]

by Jochen
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.