This is an automated email from the ASF dual-hosted git repository.

jonnybot pushed a commit to branch INDY-PERF-EXPLORATION
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit cdab2aa5ffd6f129adc7eac836384829d58c4f5d
Author: Jonny Carter <[email protected]>
AuthorDate: Fri Jan 30 18:16:16 2026 -0600

    Add further benchmarks
---
 .../bench/dispatch/InterfaceDispatchBench.java     | 165 +++++++++
 .../bench/dispatch/InterfaceDispatchHelper.groovy  | 264 ++++++++++++++
 .../bench/dispatch/MixedTypeCollectionBench.java   | 213 ++++++++++++
 .../groovy/bench/dispatch/MixedTypeHelper.groovy   | 241 +++++++++++++
 .../bench/orm/CollectionOperationsBench.java       | 216 ++++++++++++
 .../apache/groovy/bench/orm/CollectionOps.groovy   | 195 +++++++++++
 .../groovy/bench/orm/DynamicFinderBench.java       | 166 +++++++++
 .../groovy/bench/orm/DynamicFinderHelper.groovy    | 108 ++++++
 .../groovy/bench/orm/DynamicFinderService.groovy   | 122 +++++++
 .../org/apache/groovy/bench/orm/EntityGraph.groovy | 217 ++++++++++++
 .../groovy/bench/orm/EntityTraversalBench.java     | 128 +++++++
 .../bench/profiling/FlameGraphGenerator.java       | 385 +++++++++++++++++++++
 .../src/jmh/resources/workloads/cold-startup.json  |  42 +++
 .../jmh/resources/workloads/dispatch-stress.json   |  41 +++
 .../jmh/resources/workloads/grails-typical.json    |  48 +++
 .../jmh/resources/workloads/memory-intensive.json  |  44 +++
 16 files changed, 2595 insertions(+)

diff --git 
a/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/dispatch/InterfaceDispatchBench.java
 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/dispatch/InterfaceDispatchBench.java
new file mode 100644
index 0000000000..928541216f
--- /dev/null
+++ 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/dispatch/InterfaceDispatchBench.java
@@ -0,0 +1,165 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.groovy.bench.dispatch;
+
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.infra.Blackhole;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Benchmarks for interface-based polymorphic dispatch.
+ * <p>
+ * Tests scenarios where objects are accessed through interface types,
+ * which is common in service layers and plugin architectures.
+ * <p>
+ * Run with: ./gradlew -Pindy=true -PbenchInclude=InterfaceDispatch :perf:jmh
+ */
+@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Fork(2)
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+public class InterfaceDispatchBench {
+
+    private static final int ITERATIONS = 1000;
+
+    // ========================================================================
+    // State classes
+    // ========================================================================
+
+    @State(Scope.Thread)
+    public static class InterfaceState {
+        List<InterfaceService> services;
+        List<InterfaceProcessor> processors;
+        List<InterfaceValidator> validators;
+
+        @Setup(Level.Trial)
+        public void setup() {
+            services = InterfaceDispatchHelper.createServices(100);
+            processors = InterfaceDispatchHelper.createProcessors(100);
+            validators = InterfaceDispatchHelper.createValidators(100);
+        }
+    }
+
+    // ========================================================================
+    // Single interface dispatch
+    // ========================================================================
+
+    @Benchmark
+    @OperationsPerInvocation(100)
+    public void interface_singleMethod(InterfaceState state, Blackhole bh) {
+        InterfaceDispatchHelper.callExecute(state.services, bh);
+    }
+
+    @Benchmark
+    @OperationsPerInvocation(100)
+    public void interface_multipleMethodsSameInterface(InterfaceState state, 
Blackhole bh) {
+        InterfaceDispatchHelper.callMultipleMethods(state.services, bh);
+    }
+
+    // ========================================================================
+    // Multiple interfaces
+    // ========================================================================
+
+    @Benchmark
+    @OperationsPerInvocation(300)
+    public void interface_multipleInterfaces(InterfaceState state, Blackhole 
bh) {
+        InterfaceDispatchHelper.callExecute(state.services, bh);
+        InterfaceDispatchHelper.callProcess(state.processors, bh);
+        InterfaceDispatchHelper.callValidate(state.validators, bh);
+    }
+
+    // ========================================================================
+    // Typed vs untyped dispatch
+    // ========================================================================
+
+    @Benchmark
+    @OperationsPerInvocation(100)
+    public void interface_typedDispatch(InterfaceState state, Blackhole bh) {
+        InterfaceDispatchHelper.typedDispatch(state.services, bh);
+    }
+
+    @Benchmark
+    @OperationsPerInvocation(100)
+    public void interface_untypedDispatch(InterfaceState state, Blackhole bh) {
+        InterfaceDispatchHelper.untypedDispatch(state.services, bh);
+    }
+
+    // ========================================================================
+    // Interface with default methods (Java 8+)
+    // ========================================================================
+
+    @Benchmark
+    @OperationsPerInvocation(100)
+    public void interface_defaultMethod(InterfaceState state, Blackhole bh) {
+        InterfaceDispatchHelper.callDefaultMethod(state.services, bh);
+    }
+
+    // ========================================================================
+    // Chained interface calls
+    // ========================================================================
+
+    @Benchmark
+    @OperationsPerInvocation(100)
+    public void interface_chainedCalls(InterfaceState state, Blackhole bh) {
+        InterfaceDispatchHelper.chainedInterfaceCalls(state.services, 
state.processors, bh);
+    }
+
+    // ========================================================================
+    // Collection operations through interface
+    // ========================================================================
+
+    @Benchmark
+    public Object interface_collectViaInterface(InterfaceState state, 
Blackhole bh) {
+        Object result = InterfaceDispatchHelper.collectResults(state.services);
+        bh.consume(result);
+        return result;
+    }
+
+    @Benchmark
+    public Object interface_filterViaInterface(InterfaceState state, Blackhole 
bh) {
+        Object result = InterfaceDispatchHelper.filterByStatus(state.services);
+        bh.consume(result);
+        return result;
+    }
+
+    // ========================================================================
+    // Java baseline (pure invokeinterface)
+    // ========================================================================
+
+    @Benchmark
+    @OperationsPerInvocation(100)
+    public void java_interfaceDispatch(InterfaceState state, Blackhole bh) {
+        for (InterfaceService svc : state.services) {
+            bh.consume(svc.execute("test"));
+        }
+    }
+
+    @Benchmark
+    @OperationsPerInvocation(100)
+    public void java_multipleInterfaceMethods(InterfaceState state, Blackhole 
bh) {
+        for (InterfaceService svc : state.services) {
+            bh.consume(svc.execute("test"));
+            bh.consume(svc.getName());
+            bh.consume(svc.isEnabled());
+        }
+    }
+}
diff --git 
a/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/dispatch/InterfaceDispatchHelper.groovy
 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/dispatch/InterfaceDispatchHelper.groovy
new file mode 100644
index 0000000000..4aa2f5eeda
--- /dev/null
+++ 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/dispatch/InterfaceDispatchHelper.groovy
@@ -0,0 +1,264 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.groovy.bench.dispatch
+
+import org.openjdk.jmh.infra.Blackhole
+
+/**
+ * Helper for interface dispatch benchmarks.
+ * Provides interfaces and implementations that exercise polymorphic dispatch.
+ */
+class InterfaceDispatchHelper {
+
+    /**
+     * Create a list of services with different implementations.
+     */
+    static List<InterfaceService> createServices(int count) {
+        def result = []
+        count.times { i ->
+            switch (i % 4) {
+                case 0: result << new ServiceImplA(name: "ServiceA$i", 
enabled: true); break
+                case 1: result << new ServiceImplB(name: "ServiceB$i", 
enabled: i % 2 == 0); break
+                case 2: result << new ServiceImplC(name: "ServiceC$i", 
enabled: true); break
+                case 3: result << new ServiceImplD(name: "ServiceD$i", 
enabled: i % 3 == 0); break
+            }
+        }
+        result
+    }
+
+    /**
+     * Create a list of processors with different implementations.
+     */
+    static List<InterfaceProcessor> createProcessors(int count) {
+        def result = []
+        count.times { i ->
+            switch (i % 3) {
+                case 0: result << new ProcessorImplA(); break
+                case 1: result << new ProcessorImplB(); break
+                case 2: result << new ProcessorImplC(); break
+            }
+        }
+        result
+    }
+
+    /**
+     * Create a list of validators with different implementations.
+     */
+    static List<InterfaceValidator> createValidators(int count) {
+        def result = []
+        count.times { i ->
+            switch (i % 2) {
+                case 0: result << new ValidatorImplA(); break
+                case 1: result << new ValidatorImplB(); break
+            }
+        }
+        result
+    }
+
+    /**
+     * Call execute() on all services.
+     */
+    static void callExecute(List<InterfaceService> services, Blackhole bh) {
+        for (svc in services) {
+            bh.consume(svc.execute("test"))
+        }
+    }
+
+    /**
+     * Call multiple methods on same interface.
+     */
+    static void callMultipleMethods(List<InterfaceService> services, Blackhole 
bh) {
+        for (svc in services) {
+            bh.consume(svc.execute("test"))
+            bh.consume(svc.getName())
+            bh.consume(svc.isEnabled())
+        }
+    }
+
+    /**
+     * Call process() on all processors.
+     */
+    static void callProcess(List<InterfaceProcessor> processors, Blackhole bh) 
{
+        for (proc in processors) {
+            bh.consume(proc.process([1, 2, 3]))
+        }
+    }
+
+    /**
+     * Call validate() on all validators.
+     */
+    static void callValidate(List<InterfaceValidator> validators, Blackhole 
bh) {
+        for (val in validators) {
+            bh.consume(val.validate("test"))
+        }
+    }
+
+    /**
+     * Typed dispatch - explicitly typed parameter.
+     */
+    static void typedDispatch(List<InterfaceService> services, Blackhole bh) {
+        for (InterfaceService svc in services) {
+            bh.consume(svc.execute("test"))
+        }
+    }
+
+    /**
+     * Untyped dispatch - def parameter.
+     */
+    static void untypedDispatch(List services, Blackhole bh) {
+        for (def svc in services) {
+            bh.consume(svc.execute("test"))
+        }
+    }
+
+    /**
+     * Call default method on interface.
+     */
+    static void callDefaultMethod(List<InterfaceService> services, Blackhole 
bh) {
+        for (svc in services) {
+            bh.consume(svc.getDescription())
+        }
+    }
+
+    /**
+     * Chained interface calls.
+     */
+    static void chainedInterfaceCalls(List<InterfaceService> services, 
List<InterfaceProcessor> processors, Blackhole bh) {
+        services.eachWithIndex { svc, i ->
+            def result = svc.execute("input")
+            def processor = processors[i % processors.size()]
+            bh.consume(processor.process([result]))
+        }
+    }
+
+    /**
+     * Collect results via interface.
+     */
+    static List collectResults(List<InterfaceService> services) {
+        services.collect { it.execute("collect") }
+    }
+
+    /**
+     * Filter by status via interface.
+     */
+    static List filterByStatus(List<InterfaceService> services) {
+        services.findAll { it.isEnabled() }
+    }
+}
+
+// ============================================================================
+// Interface definitions
+// ============================================================================
+
+interface InterfaceService {
+    String execute(String input)
+    String getName()
+    boolean isEnabled()
+
+    // Default method (Java 8+)
+    default String getDescription() {
+        return "Service: " + getName()
+    }
+}
+
+interface InterfaceProcessor {
+    Object process(List items)
+}
+
+interface InterfaceValidator {
+    boolean validate(Object input)
+}
+
+// ============================================================================
+// Service implementations
+// ============================================================================
+
+class ServiceImplA implements InterfaceService {
+    String name
+    boolean enabled
+
+    String execute(String input) { "A:$input" }
+    String getName() { name }
+    boolean isEnabled() { enabled }
+}
+
+class ServiceImplB implements InterfaceService {
+    String name
+    boolean enabled
+
+    String execute(String input) { "B:${input.toUpperCase()}" }
+    String getName() { name }
+    boolean isEnabled() { enabled }
+}
+
+class ServiceImplC implements InterfaceService {
+    String name
+    boolean enabled
+
+    String execute(String input) { "C:${input.reverse()}" }
+    String getName() { name }
+    boolean isEnabled() { enabled }
+}
+
+class ServiceImplD implements InterfaceService {
+    String name
+    boolean enabled
+
+    String execute(String input) { "D:${input.length()}" }
+    String getName() { name }
+    boolean isEnabled() { enabled }
+}
+
+// ============================================================================
+// Processor implementations
+// ============================================================================
+
+class ProcessorImplA implements InterfaceProcessor {
+    Object process(List items) {
+        items.collect { it.toString() }.join(',')
+    }
+}
+
+class ProcessorImplB implements InterfaceProcessor {
+    Object process(List items) {
+        items.sum { it.toString().length() }
+    }
+}
+
+class ProcessorImplC implements InterfaceProcessor {
+    Object process(List items) {
+        [count: items.size(), items: items]
+    }
+}
+
+// ============================================================================
+// Validator implementations
+// ============================================================================
+
+class ValidatorImplA implements InterfaceValidator {
+    boolean validate(Object input) {
+        input != null && input.toString().length() > 0
+    }
+}
+
+class ValidatorImplB implements InterfaceValidator {
+    boolean validate(Object input) {
+        input != null && input.toString().matches(/\w+/)
+    }
+}
diff --git 
a/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/dispatch/MixedTypeCollectionBench.java
 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/dispatch/MixedTypeCollectionBench.java
new file mode 100644
index 0000000000..3ae85e53ed
--- /dev/null
+++ 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/dispatch/MixedTypeCollectionBench.java
@@ -0,0 +1,213 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.groovy.bench.dispatch;
+
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.infra.Blackhole;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Benchmarks for collections containing mixed types.
+ * <p>
+ * This is common in Groovy/Grails where lists contain objects of
+ * different domain classes (e.g., a list of search results with
+ * Person, Order, Product objects).
+ * <p>
+ * Tests the callsite cache behavior when receiver types vary.
+ * <p>
+ * Run with: ./gradlew -Pindy=true -PbenchInclude=MixedTypeCollection :perf:jmh
+ */
+@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Fork(2)
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+public class MixedTypeCollectionBench {
+
+    private static final int COLLECTION_SIZE = 100;
+
+    // ========================================================================
+    // State classes with varying type diversity
+    // ========================================================================
+
+    @State(Scope.Thread)
+    public static class Types2State {
+        List<Object> objects;
+
+        @Setup(Level.Trial)
+        public void setup() {
+            objects = MixedTypeHelper.createMixedCollection(COLLECTION_SIZE, 
2);
+        }
+    }
+
+    @State(Scope.Thread)
+    public static class Types5State {
+        List<Object> objects;
+
+        @Setup(Level.Trial)
+        public void setup() {
+            objects = MixedTypeHelper.createMixedCollection(COLLECTION_SIZE, 
5);
+        }
+    }
+
+    @State(Scope.Thread)
+    public static class Types10State {
+        List<Object> objects;
+
+        @Setup(Level.Trial)
+        public void setup() {
+            objects = MixedTypeHelper.createMixedCollection(COLLECTION_SIZE, 
10);
+        }
+    }
+
+    @State(Scope.Thread)
+    public static class Types20State {
+        List<Object> objects;
+
+        @Setup(Level.Trial)
+        public void setup() {
+            objects = MixedTypeHelper.createMixedCollection(COLLECTION_SIZE, 
20);
+        }
+    }
+
+    // ========================================================================
+    // Benchmarks with 2 types
+    // ========================================================================
+
+    @Benchmark
+    @OperationsPerInvocation(COLLECTION_SIZE)
+    public void mixed_2types_getName(Types2State state, Blackhole bh) {
+        MixedTypeHelper.callGetName(state.objects, bh);
+    }
+
+    @Benchmark
+    @OperationsPerInvocation(COLLECTION_SIZE)
+    public void mixed_2types_getValue(Types2State state, Blackhole bh) {
+        MixedTypeHelper.callGetValue(state.objects, bh);
+    }
+
+    @Benchmark
+    @OperationsPerInvocation(COLLECTION_SIZE)
+    public void mixed_2types_process(Types2State state, Blackhole bh) {
+        MixedTypeHelper.callProcess(state.objects, bh);
+    }
+
+    // ========================================================================
+    // Benchmarks with 5 types
+    // ========================================================================
+
+    @Benchmark
+    @OperationsPerInvocation(COLLECTION_SIZE)
+    public void mixed_5types_getName(Types5State state, Blackhole bh) {
+        MixedTypeHelper.callGetName(state.objects, bh);
+    }
+
+    @Benchmark
+    @OperationsPerInvocation(COLLECTION_SIZE)
+    public void mixed_5types_getValue(Types5State state, Blackhole bh) {
+        MixedTypeHelper.callGetValue(state.objects, bh);
+    }
+
+    @Benchmark
+    @OperationsPerInvocation(COLLECTION_SIZE)
+    public void mixed_5types_process(Types5State state, Blackhole bh) {
+        MixedTypeHelper.callProcess(state.objects, bh);
+    }
+
+    // ========================================================================
+    // Benchmarks with 10 types
+    // ========================================================================
+
+    @Benchmark
+    @OperationsPerInvocation(COLLECTION_SIZE)
+    public void mixed_10types_getName(Types10State state, Blackhole bh) {
+        MixedTypeHelper.callGetName(state.objects, bh);
+    }
+
+    @Benchmark
+    @OperationsPerInvocation(COLLECTION_SIZE)
+    public void mixed_10types_getValue(Types10State state, Blackhole bh) {
+        MixedTypeHelper.callGetValue(state.objects, bh);
+    }
+
+    @Benchmark
+    @OperationsPerInvocation(COLLECTION_SIZE)
+    public void mixed_10types_process(Types10State state, Blackhole bh) {
+        MixedTypeHelper.callProcess(state.objects, bh);
+    }
+
+    // ========================================================================
+    // Benchmarks with 20 types (beyond typical cache size)
+    // ========================================================================
+
+    @Benchmark
+    @OperationsPerInvocation(COLLECTION_SIZE)
+    public void mixed_20types_getName(Types20State state, Blackhole bh) {
+        MixedTypeHelper.callGetName(state.objects, bh);
+    }
+
+    @Benchmark
+    @OperationsPerInvocation(COLLECTION_SIZE)
+    public void mixed_20types_getValue(Types20State state, Blackhole bh) {
+        MixedTypeHelper.callGetValue(state.objects, bh);
+    }
+
+    @Benchmark
+    @OperationsPerInvocation(COLLECTION_SIZE)
+    public void mixed_20types_process(Types20State state, Blackhole bh) {
+        MixedTypeHelper.callProcess(state.objects, bh);
+    }
+
+    // ========================================================================
+    // Collection operations on mixed types
+    // ========================================================================
+
+    @Benchmark
+    public Object mixed_5types_collect(Types5State state, Blackhole bh) {
+        Object result = MixedTypeHelper.collectNames(state.objects);
+        bh.consume(result);
+        return result;
+    }
+
+    @Benchmark
+    public Object mixed_10types_collect(Types10State state, Blackhole bh) {
+        Object result = MixedTypeHelper.collectNames(state.objects);
+        bh.consume(result);
+        return result;
+    }
+
+    @Benchmark
+    public Object mixed_20types_collect(Types20State state, Blackhole bh) {
+        Object result = MixedTypeHelper.collectNames(state.objects);
+        bh.consume(result);
+        return result;
+    }
+
+    // ========================================================================
+    // Java interface baseline
+    // ========================================================================
+
+    @Benchmark
+    @OperationsPerInvocation(COLLECTION_SIZE)
+    public void java_interface_getName(Types10State state, Blackhole bh) {
+        MixedTypeHelper.javaInterfaceCall(state.objects, bh);
+    }
+}
diff --git 
a/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/dispatch/MixedTypeHelper.groovy
 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/dispatch/MixedTypeHelper.groovy
new file mode 100644
index 0000000000..af63333c0a
--- /dev/null
+++ 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/dispatch/MixedTypeHelper.groovy
@@ -0,0 +1,241 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.groovy.bench.dispatch
+
+import org.openjdk.jmh.infra.Blackhole
+
+/**
+ * Helper for mixed type collection benchmarks.
+ */
+class MixedTypeHelper {
+
+    // All type classes implement this interface for Java baseline
+    static interface Nameable {
+        String getName()
+        int getValue()
+        Object process()
+    }
+
+    // Generate 20 different classes
+    static final List<Class> TYPE_CLASSES = [
+        MixedType01, MixedType02, MixedType03, MixedType04, MixedType05,
+        MixedType06, MixedType07, MixedType08, MixedType09, MixedType10,
+        MixedType11, MixedType12, MixedType13, MixedType14, MixedType15,
+        MixedType16, MixedType17, MixedType18, MixedType19, MixedType20
+    ]
+
+    /**
+     * Create a collection with N different types.
+     */
+    static List<Object> createMixedCollection(int size, int numTypes) {
+        def typesToUse = TYPE_CLASSES.take(numTypes)
+        def result = []
+        size.times { i ->
+            def typeClass = typesToUse[i % numTypes]
+            result << typeClass.newInstance([name: "Item$i", value: i])
+        }
+        result
+    }
+
+    /**
+     * Call getName() on all objects (exercises callsite cache).
+     */
+    static void callGetName(List objects, Blackhole bh) {
+        for (obj in objects) {
+            bh.consume(obj.getName())
+        }
+    }
+
+    /**
+     * Call getValue() on all objects.
+     */
+    static void callGetValue(List objects, Blackhole bh) {
+        for (obj in objects) {
+            bh.consume(obj.getValue())
+        }
+    }
+
+    /**
+     * Call process() on all objects.
+     */
+    static void callProcess(List objects, Blackhole bh) {
+        for (obj in objects) {
+            bh.consume(obj.process())
+        }
+    }
+
+    /**
+     * Collect names using spread operator.
+     */
+    static List collectNames(List objects) {
+        objects*.getName()
+    }
+
+    /**
+     * Java interface call for baseline.
+     */
+    static void javaInterfaceCall(List objects, Blackhole bh) {
+        for (Object obj : objects) {
+            if (obj instanceof Nameable) {
+                bh.consume(((Nameable) obj).getName())
+            }
+        }
+    }
+}
+
+// 20 different classes with same interface
+class MixedType01 implements MixedTypeHelper.Nameable {
+    String name; int value
+    String getName() { name }
+    int getValue() { value }
+    Object process() { "01:$name" }
+}
+
+class MixedType02 implements MixedTypeHelper.Nameable {
+    String name; int value
+    String getName() { name }
+    int getValue() { value }
+    Object process() { "02:$name" }
+}
+
+class MixedType03 implements MixedTypeHelper.Nameable {
+    String name; int value
+    String getName() { name }
+    int getValue() { value }
+    Object process() { "03:$name" }
+}
+
+class MixedType04 implements MixedTypeHelper.Nameable {
+    String name; int value
+    String getName() { name }
+    int getValue() { value }
+    Object process() { "04:$name" }
+}
+
+class MixedType05 implements MixedTypeHelper.Nameable {
+    String name; int value
+    String getName() { name }
+    int getValue() { value }
+    Object process() { "05:$name" }
+}
+
+class MixedType06 implements MixedTypeHelper.Nameable {
+    String name; int value
+    String getName() { name }
+    int getValue() { value }
+    Object process() { "06:$name" }
+}
+
+class MixedType07 implements MixedTypeHelper.Nameable {
+    String name; int value
+    String getName() { name }
+    int getValue() { value }
+    Object process() { "07:$name" }
+}
+
+class MixedType08 implements MixedTypeHelper.Nameable {
+    String name; int value
+    String getName() { name }
+    int getValue() { value }
+    Object process() { "08:$name" }
+}
+
+class MixedType09 implements MixedTypeHelper.Nameable {
+    String name; int value
+    String getName() { name }
+    int getValue() { value }
+    Object process() { "09:$name" }
+}
+
+class MixedType10 implements MixedTypeHelper.Nameable {
+    String name; int value
+    String getName() { name }
+    int getValue() { value }
+    Object process() { "10:$name" }
+}
+
+class MixedType11 implements MixedTypeHelper.Nameable {
+    String name; int value
+    String getName() { name }
+    int getValue() { value }
+    Object process() { "11:$name" }
+}
+
+class MixedType12 implements MixedTypeHelper.Nameable {
+    String name; int value
+    String getName() { name }
+    int getValue() { value }
+    Object process() { "12:$name" }
+}
+
+class MixedType13 implements MixedTypeHelper.Nameable {
+    String name; int value
+    String getName() { name }
+    int getValue() { value }
+    Object process() { "13:$name" }
+}
+
+class MixedType14 implements MixedTypeHelper.Nameable {
+    String name; int value
+    String getName() { name }
+    int getValue() { value }
+    Object process() { "14:$name" }
+}
+
+class MixedType15 implements MixedTypeHelper.Nameable {
+    String name; int value
+    String getName() { name }
+    int getValue() { value }
+    Object process() { "15:$name" }
+}
+
+class MixedType16 implements MixedTypeHelper.Nameable {
+    String name; int value
+    String getName() { name }
+    int getValue() { value }
+    Object process() { "16:$name" }
+}
+
+class MixedType17 implements MixedTypeHelper.Nameable {
+    String name; int value
+    String getName() { name }
+    int getValue() { value }
+    Object process() { "17:$name" }
+}
+
+class MixedType18 implements MixedTypeHelper.Nameable {
+    String name; int value
+    String getName() { name }
+    int getValue() { value }
+    Object process() { "18:$name" }
+}
+
+class MixedType19 implements MixedTypeHelper.Nameable {
+    String name; int value
+    String getName() { name }
+    int getValue() { value }
+    Object process() { "19:$name" }
+}
+
+class MixedType20 implements MixedTypeHelper.Nameable {
+    String name; int value
+    String getName() { name }
+    int getValue() { value }
+    Object process() { "20:$name" }
+}
diff --git 
a/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/CollectionOperationsBench.java
 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/CollectionOperationsBench.java
new file mode 100644
index 0000000000..53c380cdae
--- /dev/null
+++ 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/CollectionOperationsBench.java
@@ -0,0 +1,216 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.groovy.bench.orm;
+
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.infra.Blackhole;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Benchmarks for Groovy collection operations on domain objects.
+ * <p>
+ * Tests common GORM/Grails patterns:
+ * - .each { } iteration
+ * - .collect { } transformation
+ * - .findAll { } filtering
+ * - .groupBy { } aggregation
+ * - .sum { }, .max { }, .min { }
+ * <p>
+ * Run with: ./gradlew -Pindy=true -PbenchInclude=CollectionOperations 
:perf:jmh
+ */
+@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Fork(2)
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+public class CollectionOperationsBench {
+
+    private static final int COLLECTION_SIZE = 1000;
+
+    @State(Scope.Thread)
+    public static class CollectionState {
+        CollectionOps ops;
+        List<CollectionDomain> domains;
+
+        @Setup(Level.Trial)
+        public void setup() {
+            ops = new CollectionOps();
+            domains = ops.createDomains(COLLECTION_SIZE);
+        }
+    }
+
+    // ========================================================================
+    // Basic iteration
+    // ========================================================================
+
+    @Benchmark
+    public void collection_each(CollectionState state, Blackhole bh) {
+        state.ops.doEach(state.domains, bh);
+    }
+
+    @Benchmark
+    public void collection_eachWithIndex(CollectionState state, Blackhole bh) {
+        state.ops.doEachWithIndex(state.domains, bh);
+    }
+
+    // ========================================================================
+    // Transformation
+    // ========================================================================
+
+    @Benchmark
+    public Object collection_collect(CollectionState state, Blackhole bh) {
+        Object result = state.ops.doCollect(state.domains);
+        bh.consume(result);
+        return result;
+    }
+
+    @Benchmark
+    public Object collection_collectNested(CollectionState state, Blackhole 
bh) {
+        Object result = state.ops.doCollectNested(state.domains);
+        bh.consume(result);
+        return result;
+    }
+
+    @Benchmark
+    public Object collection_collectMany(CollectionState state, Blackhole bh) {
+        Object result = state.ops.doCollectMany(state.domains);
+        bh.consume(result);
+        return result;
+    }
+
+    // ========================================================================
+    // Filtering
+    // ========================================================================
+
+    @Benchmark
+    public Object collection_findAll(CollectionState state, Blackhole bh) {
+        Object result = state.ops.doFindAll(state.domains);
+        bh.consume(result);
+        return result;
+    }
+
+    @Benchmark
+    public Object collection_find(CollectionState state, Blackhole bh) {
+        Object result = state.ops.doFind(state.domains);
+        bh.consume(result);
+        return result;
+    }
+
+    @Benchmark
+    public Object collection_grep(CollectionState state, Blackhole bh) {
+        Object result = state.ops.doGrep(state.domains);
+        bh.consume(result);
+        return result;
+    }
+
+    // ========================================================================
+    // Aggregation
+    // ========================================================================
+
+    @Benchmark
+    public Object collection_groupBy(CollectionState state, Blackhole bh) {
+        Object result = state.ops.doGroupBy(state.domains);
+        bh.consume(result);
+        return result;
+    }
+
+    @Benchmark
+    public Object collection_countBy(CollectionState state, Blackhole bh) {
+        Object result = state.ops.doCountBy(state.domains);
+        bh.consume(result);
+        return result;
+    }
+
+    @Benchmark
+    public Object collection_sum(CollectionState state, Blackhole bh) {
+        Object result = state.ops.doSum(state.domains);
+        bh.consume(result);
+        return result;
+    }
+
+    @Benchmark
+    public Object collection_max(CollectionState state, Blackhole bh) {
+        Object result = state.ops.doMax(state.domains);
+        bh.consume(result);
+        return result;
+    }
+
+    // ========================================================================
+    // Chained operations (common in Grails)
+    // ========================================================================
+
+    @Benchmark
+    public Object collection_chainedOps(CollectionState state, Blackhole bh) {
+        Object result = state.ops.doChainedOperations(state.domains);
+        bh.consume(result);
+        return result;
+    }
+
+    @Benchmark
+    public Object collection_complexChain(CollectionState state, Blackhole bh) 
{
+        Object result = state.ops.doComplexChain(state.domains);
+        bh.consume(result);
+        return result;
+    }
+
+    // ========================================================================
+    // Sorting
+    // ========================================================================
+
+    @Benchmark
+    public Object collection_sort(CollectionState state, Blackhole bh) {
+        Object result = state.ops.doSort(state.domains);
+        bh.consume(result);
+        return result;
+    }
+
+    @Benchmark
+    public Object collection_sortBy(CollectionState state, Blackhole bh) {
+        Object result = state.ops.doSortBy(state.domains);
+        bh.consume(result);
+        return result;
+    }
+
+    // ========================================================================
+    // Java Stream baseline
+    // ========================================================================
+
+    @Benchmark
+    public Object java_streamCollect(CollectionState state, Blackhole bh) {
+        Object result = state.ops.javaStreamCollect(state.domains);
+        bh.consume(result);
+        return result;
+    }
+
+    @Benchmark
+    public Object java_streamFilter(CollectionState state, Blackhole bh) {
+        Object result = state.ops.javaStreamFilter(state.domains);
+        bh.consume(result);
+        return result;
+    }
+
+    @Benchmark
+    public Object java_streamChained(CollectionState state, Blackhole bh) {
+        Object result = state.ops.javaStreamChained(state.domains);
+        bh.consume(result);
+        return result;
+    }
+}
diff --git 
a/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/CollectionOps.groovy
 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/CollectionOps.groovy
new file mode 100644
index 0000000000..290943a583
--- /dev/null
+++ 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/CollectionOps.groovy
@@ -0,0 +1,195 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.groovy.bench.orm
+
+import org.openjdk.jmh.infra.Blackhole
+import java.util.stream.Collectors
+
+/**
+ * Collection operations helper for benchmarks.
+ */
+class CollectionOps {
+
+    List<CollectionDomain> createDomains(int count) {
+        def random = new Random(42)
+        def categories = ['A', 'B', 'C', 'D', 'E']
+        def statuses = ['ACTIVE', 'INACTIVE', 'PENDING', 'ARCHIVED']
+
+        (0..<count).collect { i ->
+            new CollectionDomain(
+                id: i,
+                name: "Domain $i",
+                category: categories[random.nextInt(categories.size())],
+                status: statuses[random.nextInt(statuses.size())],
+                value: random.nextInt(10000),
+                tags: (0..<(random.nextInt(5) + 1)).collect { "tag$it" }
+            )
+        }
+    }
+
+    // ========================================================================
+    // Basic iteration
+    // ========================================================================
+
+    void doEach(List<CollectionDomain> domains, Blackhole bh) {
+        domains.each { domain ->
+            bh.consume(domain.name)
+            bh.consume(domain.value)
+        }
+    }
+
+    void doEachWithIndex(List<CollectionDomain> domains, Blackhole bh) {
+        domains.eachWithIndex { domain, idx ->
+            bh.consume(domain.name)
+            bh.consume(idx)
+        }
+    }
+
+    // ========================================================================
+    // Transformation
+    // ========================================================================
+
+    List doCollect(List<CollectionDomain> domains) {
+        domains.collect { it.name }
+    }
+
+    List doCollectNested(List<CollectionDomain> domains) {
+        domains.collect { domain ->
+            [
+                id: domain.id,
+                name: domain.name,
+                upperName: domain.name.toUpperCase(),
+                valueDoubled: domain.value * 2
+            ]
+        }
+    }
+
+    List doCollectMany(List<CollectionDomain> domains) {
+        domains.collectMany { it.tags }
+    }
+
+    // ========================================================================
+    // Filtering
+    // ========================================================================
+
+    List doFindAll(List<CollectionDomain> domains) {
+        domains.findAll { it.status == 'ACTIVE' && it.value > 5000 }
+    }
+
+    CollectionDomain doFind(List<CollectionDomain> domains) {
+        domains.find { it.value > 9000 }
+    }
+
+    List doGrep(List<CollectionDomain> domains) {
+        domains.grep { it.category == 'A' }
+    }
+
+    // ========================================================================
+    // Aggregation
+    // ========================================================================
+
+    Map doGroupBy(List<CollectionDomain> domains) {
+        domains.groupBy { it.category }
+    }
+
+    Map doCountBy(List<CollectionDomain> domains) {
+        domains.countBy { it.status }
+    }
+
+    def doSum(List<CollectionDomain> domains) {
+        domains.sum { it.value }
+    }
+
+    CollectionDomain doMax(List<CollectionDomain> domains) {
+        domains.max { it.value }
+    }
+
+    // ========================================================================
+    // Chained operations
+    // ========================================================================
+
+    List doChainedOperations(List<CollectionDomain> domains) {
+        domains.findAll { it.status == 'ACTIVE' }
+               .collect { it.name }
+               .unique()
+               .sort()
+    }
+
+    Map doComplexChain(List<CollectionDomain> domains) {
+        domains.findAll { it.value > 1000 }
+               .groupBy { it.category }
+               .collectEntries { category, items ->
+                   [category, [
+                       count: items.size(),
+                       total: items.sum { it.value },
+                       avg: items.sum { it.value } / items.size(),
+                       names: items*.name
+                   ]]
+               }
+    }
+
+    // ========================================================================
+    // Sorting
+    // ========================================================================
+
+    List doSort(List<CollectionDomain> domains) {
+        domains.sort { it.name }
+    }
+
+    List doSortBy(List<CollectionDomain> domains) {
+        domains.sort { a, b -> b.value <=> a.value }
+    }
+
+    // ========================================================================
+    // Java Stream baseline
+    // ========================================================================
+
+    List<String> javaStreamCollect(List<CollectionDomain> domains) {
+        domains.stream()
+               .map { it.name }
+               .collect(Collectors.toList())
+    }
+
+    List<CollectionDomain> javaStreamFilter(List<CollectionDomain> domains) {
+        domains.stream()
+               .filter { it.status == 'ACTIVE' && it.value > 5000 }
+               .collect(Collectors.toList())
+    }
+
+    List<String> javaStreamChained(List<CollectionDomain> domains) {
+        domains.stream()
+               .filter { it.status == 'ACTIVE' }
+               .map { it.name }
+               .distinct()
+               .sorted()
+               .collect(Collectors.toList())
+    }
+}
+
+/**
+ * Domain class for collection operations.
+ */
+class CollectionDomain {
+    long id
+    String name
+    String category
+    String status
+    int value
+    List<String> tags = []
+}
diff --git 
a/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/DynamicFinderBench.java
 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/DynamicFinderBench.java
new file mode 100644
index 0000000000..3ca5e02c5c
--- /dev/null
+++ 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/DynamicFinderBench.java
@@ -0,0 +1,166 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.groovy.bench.orm;
+
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.infra.Blackhole;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Benchmarks simulating GORM dynamic finder patterns.
+ * <p>
+ * Dynamic finders like findByName(), findAllByStatus() use methodMissing
+ * which exercises Groovy's dynamic dispatch heavily.
+ * <p>
+ * Run with: ./gradlew -Pindy=true -PbenchInclude=DynamicFinder :perf:jmh
+ */
+@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Fork(2)
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+public class DynamicFinderBench {
+
+    @State(Scope.Thread)
+    public static class FinderState {
+        DynamicFinderService service;
+        Random random;
+
+        @Setup(Level.Trial)
+        public void setup() {
+            random = new Random(42);
+            service = new DynamicFinderService();
+
+            // Seed with test data
+            for (int i = 0; i < 1000; i++) {
+                service.addPerson("First" + i, "Last" + (i % 100), "person" + 
i + "@example.com", i % 50);
+            }
+        }
+    }
+
+    // ========================================================================
+    // Single property finders
+    // ========================================================================
+
+    @Benchmark
+    public Object finder_findByEmail(FinderState state, Blackhole bh) {
+        Object result = DynamicFinderHelper.findByEmail(state.service, 
"[email protected]");
+        bh.consume(result);
+        return result;
+    }
+
+    @Benchmark
+    public Object finder_findByLastName(FinderState state, Blackhole bh) {
+        Object result = DynamicFinderHelper.findByLastName(state.service, 
"Last25");
+        bh.consume(result);
+        return result;
+    }
+
+    @Benchmark
+    public Object finder_findByAge(FinderState state, Blackhole bh) {
+        Object result = DynamicFinderHelper.findByAge(state.service, 30);
+        bh.consume(result);
+        return result;
+    }
+
+    // ========================================================================
+    // FindAll finders (return lists)
+    // ========================================================================
+
+    @Benchmark
+    public Object finder_findAllByLastName(FinderState state, Blackhole bh) {
+        Object result = DynamicFinderHelper.findAllByLastName(state.service, 
"Last25");
+        bh.consume(result);
+        return result;
+    }
+
+    @Benchmark
+    public Object finder_findAllByAge(FinderState state, Blackhole bh) {
+        Object result = DynamicFinderHelper.findAllByAge(state.service, 30);
+        bh.consume(result);
+        return result;
+    }
+
+    // ========================================================================
+    // Compound finders
+    // ========================================================================
+
+    @Benchmark
+    public Object finder_findByFirstNameAndLastName(FinderState state, 
Blackhole bh) {
+        Object result = 
DynamicFinderHelper.findByFirstNameAndLastName(state.service, "First25", 
"Last25");
+        bh.consume(result);
+        return result;
+    }
+
+    @Benchmark
+    public Object finder_findAllByLastNameOrAge(FinderState state, Blackhole 
bh) {
+        Object result = 
DynamicFinderHelper.findAllByLastNameOrAge(state.service, "Last25", 30);
+        bh.consume(result);
+        return result;
+    }
+
+    // ========================================================================
+    // Count finders
+    // ========================================================================
+
+    @Benchmark
+    public Object finder_countByLastName(FinderState state, Blackhole bh) {
+        Object result = DynamicFinderHelper.countByLastName(state.service, 
"Last25");
+        bh.consume(result);
+        return result;
+    }
+
+    @Benchmark
+    public Object finder_countByAge(FinderState state, Blackhole bh) {
+        Object result = DynamicFinderHelper.countByAge(state.service, 30);
+        bh.consume(result);
+        return result;
+    }
+
+    // ========================================================================
+    // Mixed finder patterns (realistic usage)
+    // ========================================================================
+
+    @Benchmark
+    public Object finder_mixedQueries(FinderState state, Blackhole bh) {
+        int idx = state.random.nextInt(100);
+        DynamicFinderHelper.mixedFinderOperations(state.service, idx, bh);
+        return idx;
+    }
+
+    // ========================================================================
+    // Java baseline (direct method calls)
+    // ========================================================================
+
+    @Benchmark
+    public Object java_findByEmail(FinderState state, Blackhole bh) {
+        Object result = state.service.javaFindByEmail("[email protected]");
+        bh.consume(result);
+        return result;
+    }
+
+    @Benchmark
+    public Object java_findAllByLastName(FinderState state, Blackhole bh) {
+        Object result = state.service.javaFindAllByLastName("Last25");
+        bh.consume(result);
+        return result;
+    }
+}
diff --git 
a/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/DynamicFinderHelper.groovy
 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/DynamicFinderHelper.groovy
new file mode 100644
index 0000000000..7f354e9249
--- /dev/null
+++ 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/DynamicFinderHelper.groovy
@@ -0,0 +1,108 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.groovy.bench.orm
+
+import org.openjdk.jmh.infra.Blackhole
+
+/**
+ * Helper for invoking dynamic finder methods from Java benchmarks.
+ * Groovy code can call methodMissing-based dynamic methods directly.
+ */
+class DynamicFinderHelper {
+
+    // ========================================================================
+    // findByX methods
+    // ========================================================================
+
+    static Object findByEmail(DynamicFinderService service, String email) {
+        service.findByEmail(email)
+    }
+
+    static Object findByLastName(DynamicFinderService service, String 
lastName) {
+        service.findByLastName(lastName)
+    }
+
+    static Object findByAge(DynamicFinderService service, int age) {
+        service.findByAge(age)
+    }
+
+    static Object findByFirstNameAndLastName(DynamicFinderService service, 
String firstName, String lastName) {
+        service.findByFirstNameAndLastName(firstName, lastName)
+    }
+
+    // ========================================================================
+    // findAllByX methods
+    // ========================================================================
+
+    static Object findAllByLastName(DynamicFinderService service, String 
lastName) {
+        service.findAllByLastName(lastName)
+    }
+
+    static Object findAllByAge(DynamicFinderService service, int age) {
+        service.findAllByAge(age)
+    }
+
+    static Object findAllByLastNameOrAge(DynamicFinderService service, String 
lastName, int age) {
+        service.findAllByLastNameOrAge(lastName, age)
+    }
+
+    // ========================================================================
+    // countByX methods
+    // ========================================================================
+
+    static Object countByLastName(DynamicFinderService service, String 
lastName) {
+        service.countByLastName(lastName)
+    }
+
+    static Object countByAge(DynamicFinderService service, int age) {
+        service.countByAge(age)
+    }
+
+    // ========================================================================
+    // Mixed operations for benchmarks
+    // ========================================================================
+
+    static void mixedFinderOperations(DynamicFinderService service, int idx, 
Blackhole bh) {
+        bh.consume(service.findByEmail("person" + idx + "@example.com"))
+        bh.consume(service.findAllByLastName("Last" + (idx % 100)))
+        bh.consume(service.countByAge(idx % 50))
+    }
+
+    // ========================================================================
+    // Batch operations
+    // ========================================================================
+
+    static void batchFindByEmail(DynamicFinderService service, int count, 
Blackhole bh) {
+        for (int i = 0; i < count; i++) {
+            bh.consume(service.findByEmail("person" + i + "@example.com"))
+        }
+    }
+
+    static void batchFindAllByLastName(DynamicFinderService service, int 
count, Blackhole bh) {
+        for (int i = 0; i < count; i++) {
+            bh.consume(service.findAllByLastName("Last" + (i % 100)))
+        }
+    }
+
+    static void batchCountByAge(DynamicFinderService service, int count, 
Blackhole bh) {
+        for (int i = 0; i < count; i++) {
+            bh.consume(service.countByAge(i % 50))
+        }
+    }
+}
diff --git 
a/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/DynamicFinderService.groovy
 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/DynamicFinderService.groovy
new file mode 100644
index 0000000000..5a260f59a8
--- /dev/null
+++ 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/DynamicFinderService.groovy
@@ -0,0 +1,122 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.groovy.bench.orm
+
+/**
+ * Service that implements GORM-like dynamic finders via methodMissing.
+ * This exercises Groovy's dynamic method dispatch heavily.
+ */
+class DynamicFinderService {
+
+    List<FinderPerson> people = []
+
+    void addPerson(String firstName, String lastName, String email, int age) {
+        people << new FinderPerson(
+            firstName: firstName,
+            lastName: lastName,
+            email: email,
+            age: age
+        )
+    }
+
+    /**
+     * methodMissing implements dynamic finders like:
+     * - findByX(value)
+     * - findAllByX(value)
+     * - countByX(value)
+     * - findByXAndY(value1, value2)
+     * - findAllByXOrY(value1, value2)
+     */
+    def methodMissing(String name, args) {
+        if (name.startsWith('findAllBy')) {
+            return handleFindAllBy(name.substring(9), args)
+        } else if (name.startsWith('findBy')) {
+            return handleFindBy(name.substring(6), args)
+        } else if (name.startsWith('countBy')) {
+            return handleCountBy(name.substring(7), args)
+        }
+        throw new MissingMethodException(name, this.class, args as Object[])
+    }
+
+    private Object handleFindBy(String expression, args) {
+        def predicate = buildPredicate(expression, args)
+        people.find(predicate)
+    }
+
+    private List handleFindAllBy(String expression, args) {
+        def predicate = buildPredicate(expression, args)
+        people.findAll(predicate)
+    }
+
+    private int handleCountBy(String expression, args) {
+        def predicate = buildPredicate(expression, args)
+        people.count(predicate)
+    }
+
+    private Closure buildPredicate(String expression, args) {
+        // Handle compound expressions: XAndY, XOrY
+        if (expression.contains('And')) {
+            def parts = expression.split('And')
+            def prop1 = parts[0].uncapitalize()
+            def prop2 = parts[1].uncapitalize()
+            return { it."$prop1" == args[0] && it."$prop2" == args[1] }
+        } else if (expression.contains('Or')) {
+            def parts = expression.split('Or')
+            def prop1 = parts[0].uncapitalize()
+            def prop2 = parts[1].uncapitalize()
+            return { it."$prop1" == args[0] || it."$prop2" == args[1] }
+        } else {
+            // Simple property
+            def prop = expression.uncapitalize()
+            return { it."$prop" == args[0] }
+        }
+    }
+
+    // Java-style direct methods for baseline comparison
+    FinderPerson javaFindByEmail(String email) {
+        for (FinderPerson p : people) {
+            if (email.equals(p.email)) {
+                return p
+            }
+        }
+        return null
+    }
+
+    List<FinderPerson> javaFindAllByLastName(String lastName) {
+        List<FinderPerson> result = new ArrayList<>()
+        for (FinderPerson p : people) {
+            if (lastName.equals(p.lastName)) {
+                result.add(p)
+            }
+        }
+        return result
+    }
+}
+
+/**
+ * Simple person class for finder tests.
+ */
+class FinderPerson {
+    String firstName
+    String lastName
+    String email
+    int age
+
+    String getFullName() { "$firstName $lastName" }
+}
diff --git 
a/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/EntityGraph.groovy
 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/EntityGraph.groovy
new file mode 100644
index 0000000000..fbbb537b90
--- /dev/null
+++ 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/EntityGraph.groovy
@@ -0,0 +1,217 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.groovy.bench.orm
+
+import org.openjdk.jmh.infra.Blackhole
+
+/**
+ * Entity graph for traversal benchmarks.
+ * Simulates GORM-style object relationships.
+ */
+class EntityGraph {
+
+    List<GraphCustomer> customers = []
+
+    void buildGraph(int customerCount) {
+        def random = new Random(42)
+        def categories = ['Electronics', 'Clothing', 'Books', 'Home', 
'Sports'].collect {
+            new GraphCategory(name: it, code: it.take(3).toUpperCase())
+        }
+
+        customerCount.times { i ->
+            def customer = new GraphCustomer(
+                name: "Customer $i",
+                email: "customer${i}@example.com",
+                address: new GraphAddress(
+                    street: "${i * 10} Main St",
+                    city: "City${i % 10}",
+                    country: new GraphCountry(
+                        name: "Country${i % 5}",
+                        code: "C${i % 5}"
+                    )
+                )
+            )
+
+            // Add 1-5 orders per customer
+            (random.nextInt(5) + 1).times { j ->
+                def order = new GraphOrder(
+                    orderNumber: "ORD-$i-$j",
+                    customer: customer
+                )
+
+                // Add 1-10 items per order
+                (random.nextInt(10) + 1).times { k ->
+                    def product = new GraphProduct(
+                        name: "Product $i-$j-$k",
+                        sku: "SKU-$i-$j-$k",
+                        category: categories[random.nextInt(categories.size())]
+                    )
+                    order.items << new GraphOrderItem(
+                        product: product,
+                        quantity: random.nextInt(5) + 1,
+                        price: (random.nextInt(10000) + 100) / 100.0
+                    )
+                }
+
+                customer.orders << order
+            }
+
+            customers << customer
+        }
+    }
+
+    // ========================================================================
+    // Traversal methods for benchmarks
+    // ========================================================================
+
+    void traverseSimpleProperty(Blackhole bh) {
+        for (customer in customers) {
+            bh.consume(customer.name)
+            bh.consume(customer.email)
+        }
+    }
+
+    void traverseNestedProperty(Blackhole bh) {
+        for (customer in customers) {
+            bh.consume(customer.address.city)
+            bh.consume(customer.address.street)
+        }
+    }
+
+    void traverseDeepProperty(Blackhole bh) {
+        for (customer in customers) {
+            bh.consume(customer.address.country.name)
+            bh.consume(customer.address.country.code)
+        }
+    }
+
+    void traverseVeryDeepProperty(Blackhole bh) {
+        for (customer in customers) {
+            for (order in customer.orders) {
+                for (item in order.items) {
+                    bh.consume(item.product.category.name)
+                }
+            }
+        }
+    }
+
+    void traverseWithSpread(Blackhole bh) {
+        for (customer in customers) {
+            def orderNumbers = customer.orders*.orderNumber
+            bh.consume(orderNumbers)
+        }
+    }
+
+    void traverseNestedSpread(Blackhole bh) {
+        for (customer in customers) {
+            def productNames = customer.orders*.items.flatten()*.product*.name
+            bh.consume(productNames)
+        }
+    }
+
+    void traverseNullSafe(Blackhole bh) {
+        for (customer in customers) {
+            // Some might be null in real scenarios
+            bh.consume(customer?.address?.country?.name)
+            
bh.consume(customer?.orders?.first()?.items?.first()?.product?.name)
+        }
+    }
+
+    void traverseMixedPatterns(Blackhole bh) {
+        for (customer in customers) {
+            // Simple
+            bh.consume(customer.name)
+
+            // Nested
+            bh.consume(customer.address.city)
+
+            // Deep
+            bh.consume(customer.address.country.name)
+
+            // Spread
+            bh.consume(customer.orders*.orderNumber)
+
+            // Collection with closure
+            def totals = customer.orders.collect { order ->
+                order.items.sum { it.price * it.quantity }
+            }
+            bh.consume(totals)
+        }
+    }
+
+    // ========================================================================
+    // Java baseline methods
+    // ========================================================================
+
+    void javaTraverseSimple(Blackhole bh) {
+        for (GraphCustomer customer : customers) {
+            bh.consume(customer.getName())
+            bh.consume(customer.getEmail())
+        }
+    }
+
+    void javaTraverseDeep(Blackhole bh) {
+        for (GraphCustomer customer : customers) {
+            bh.consume(customer.getAddress().getCountry().getName())
+            bh.consume(customer.getAddress().getCountry().getCode())
+        }
+    }
+}
+
+// Domain classes for graph traversal
+class GraphCustomer {
+    String name
+    String email
+    GraphAddress address
+    List<GraphOrder> orders = []
+}
+
+class GraphAddress {
+    String street
+    String city
+    GraphCountry country
+}
+
+class GraphCountry {
+    String name
+    String code
+}
+
+class GraphOrder {
+    String orderNumber
+    GraphCustomer customer
+    List<GraphOrderItem> items = []
+}
+
+class GraphOrderItem {
+    GraphProduct product
+    int quantity
+    BigDecimal price
+}
+
+class GraphProduct {
+    String name
+    String sku
+    GraphCategory category
+}
+
+class GraphCategory {
+    String name
+    String code
+}
diff --git 
a/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/EntityTraversalBench.java
 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/EntityTraversalBench.java
new file mode 100644
index 0000000000..3357013eba
--- /dev/null
+++ 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/orm/EntityTraversalBench.java
@@ -0,0 +1,128 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.groovy.bench.orm;
+
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.infra.Blackhole;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Benchmarks for object graph navigation patterns common in GORM/Grails.
+ * <p>
+ * Tests patterns like:
+ * - person.address.city
+ * - order.customer.address.country
+ * - order.items*.product.category
+ * <p>
+ * Run with: ./gradlew -Pindy=true -PbenchInclude=EntityTraversal :perf:jmh
+ */
+@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Fork(2)
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+public class EntityTraversalBench {
+
+    @State(Scope.Thread)
+    public static class TraversalState {
+        EntityGraph graph;
+
+        @Setup(Level.Trial)
+        public void setup() {
+            graph = new EntityGraph();
+            graph.buildGraph(100); // 100 customers with orders
+        }
+    }
+
+    // ========================================================================
+    // Simple traversal (2 levels)
+    // ========================================================================
+
+    @Benchmark
+    public void traversal_simpleProperty(TraversalState state, Blackhole bh) {
+        state.graph.traverseSimpleProperty(bh);
+    }
+
+    @Benchmark
+    public void traversal_nestedProperty(TraversalState state, Blackhole bh) {
+        state.graph.traverseNestedProperty(bh);
+    }
+
+    // ========================================================================
+    // Deep traversal (3+ levels)
+    // ========================================================================
+
+    @Benchmark
+    public void traversal_deepProperty(TraversalState state, Blackhole bh) {
+        state.graph.traverseDeepProperty(bh);
+    }
+
+    @Benchmark
+    public void traversal_veryDeepProperty(TraversalState state, Blackhole bh) 
{
+        state.graph.traverseVeryDeepProperty(bh);
+    }
+
+    // ========================================================================
+    // Collection traversal with spread
+    // ========================================================================
+
+    @Benchmark
+    public void traversal_spreadOnCollection(TraversalState state, Blackhole 
bh) {
+        state.graph.traverseWithSpread(bh);
+    }
+
+    @Benchmark
+    public void traversal_nestedSpread(TraversalState state, Blackhole bh) {
+        state.graph.traverseNestedSpread(bh);
+    }
+
+    // ========================================================================
+    // Null-safe traversal
+    // ========================================================================
+
+    @Benchmark
+    public void traversal_nullSafe(TraversalState state, Blackhole bh) {
+        state.graph.traverseNullSafe(bh);
+    }
+
+    // ========================================================================
+    // Mixed traversal patterns
+    // ========================================================================
+
+    @Benchmark
+    public void traversal_mixedPatterns(TraversalState state, Blackhole bh) {
+        state.graph.traverseMixedPatterns(bh);
+    }
+
+    // ========================================================================
+    // Java baseline
+    // ========================================================================
+
+    @Benchmark
+    public void java_simpleTraversal(TraversalState state, Blackhole bh) {
+        state.graph.javaTraverseSimple(bh);
+    }
+
+    @Benchmark
+    public void java_deepTraversal(TraversalState state, Blackhole bh) {
+        state.graph.javaTraverseDeep(bh);
+    }
+}
diff --git 
a/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/profiling/FlameGraphGenerator.java
 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/profiling/FlameGraphGenerator.java
new file mode 100644
index 0000000000..8b1df6db7d
--- /dev/null
+++ 
b/subprojects/performance/src/jmh/groovy/org/apache/groovy/bench/profiling/FlameGraphGenerator.java
@@ -0,0 +1,385 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.groovy.bench.profiling;
+
+import java.io.*;
+import java.nio.file.*;
+import java.util.*;
+import java.util.regex.*;
+
+/**
+ * Post-processes JFR output to generate flame graph compatible output.
+ * <p>
+ * This class converts JFR recording data to the collapsed stack format
+ * that can be consumed by flame graph tools like async-profiler's
+ * converter or Brendan Gregg's flamegraph.pl.
+ * <p>
+ * Usage:
+ * <pre>
+ * # First, convert JFR to text using jfr tool
+ * jfr print --events jdk.ExecutionSample recording.jfr > stacks.txt
+ *
+ * # Then use this tool to convert to collapsed format
+ * java FlameGraphGenerator stacks.txt > collapsed.txt
+ *
+ * # Finally generate SVG using flamegraph.pl
+ * flamegraph.pl collapsed.txt > profile.svg
+ * </pre>
+ */
+public class FlameGraphGenerator {
+
+    private static final Pattern STACK_FRAME_PATTERN =
+            Pattern.compile("^\\s+(.+)$");
+
+    private static final Pattern EVENT_SEPARATOR_PATTERN =
+            
Pattern.compile("^jdk\\.(ExecutionSample|NativeMethodSample|ObjectAllocationInNewTLAB|ObjectAllocationOutsideTLAB)");
+
+    /**
+     * Main entry point for command-line usage.
+     */
+    public static void main(String[] args) throws Exception {
+        if (args.length < 1) {
+            printUsage();
+            System.exit(1);
+        }
+
+        String command = args[0];
+
+        switch (command) {
+            case "collapse":
+                if (args.length < 2) {
+                    System.err.println("Usage: FlameGraphGenerator collapse 
<jfr-text-file>");
+                    System.exit(1);
+                }
+                collapseStacks(args[1], System.out);
+                break;
+
+            case "filter":
+                if (args.length < 3) {
+                    System.err.println("Usage: FlameGraphGenerator filter 
<collapsed-file> <pattern>");
+                    System.exit(1);
+                }
+                filterStacks(args[1], args[2], System.out);
+                break;
+
+            case "top":
+                if (args.length < 2) {
+                    System.err.println("Usage: FlameGraphGenerator top 
<collapsed-file> [n]");
+                    System.exit(1);
+                }
+                int n = args.length > 2 ? Integer.parseInt(args[2]) : 20;
+                topMethods(args[1], n, System.out);
+                break;
+
+            case "diff":
+                if (args.length < 3) {
+                    System.err.println("Usage: FlameGraphGenerator diff 
<baseline-file> <current-file>");
+                    System.exit(1);
+                }
+                diffProfiles(args[1], args[2], System.out);
+                break;
+
+            default:
+                printUsage();
+                System.exit(1);
+        }
+    }
+
+    private static void printUsage() {
+        System.err.println("FlameGraphGenerator - JFR profile analysis tool 
for Groovy benchmarks");
+        System.err.println();
+        System.err.println("Commands:");
+        System.err.println("  collapse <jfr-text>     Convert JFR text output 
to collapsed stacks");
+        System.err.println("  filter <collapsed> <pattern>  Filter stacks 
matching pattern");
+        System.err.println("  top <collapsed> [n]     Show top n methods by 
sample count");
+        System.err.println("  diff <baseline> <current>  Show difference 
between profiles");
+        System.err.println();
+        System.err.println("Workflow:");
+        System.err.println("  1. jfr print --events jdk.ExecutionSample 
recording.jfr > stacks.txt");
+        System.err.println("  2. java FlameGraphGenerator collapse stacks.txt 
> collapsed.txt");
+        System.err.println("  3. flamegraph.pl collapsed.txt > profile.svg");
+        System.err.println();
+        System.err.println("Analysis:");
+        System.err.println("  java FlameGraphGenerator filter collapsed.txt 
'groovy' > groovy-only.txt");
+        System.err.println("  java FlameGraphGenerator top collapsed.txt 30");
+        System.err.println("  java FlameGraphGenerator diff baseline.txt 
current.txt");
+    }
+
+    /**
+     * Convert JFR text output to collapsed stack format.
+     */
+    public static void collapseStacks(String inputFile, PrintStream out) 
throws IOException {
+        Map<String, Long> stackCounts = new LinkedHashMap<>();
+        List<String> currentStack = new ArrayList<>();
+
+        try (BufferedReader reader = 
Files.newBufferedReader(Paths.get(inputFile))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                Matcher eventMatcher = EVENT_SEPARATOR_PATTERN.matcher(line);
+                if (eventMatcher.find()) {
+                    // Start of new event - save previous stack if any
+                    if (!currentStack.isEmpty()) {
+                        String collapsed = collapseStack(currentStack);
+                        stackCounts.merge(collapsed, 1L, Long::sum);
+                        currentStack.clear();
+                    }
+                    continue;
+                }
+
+                Matcher frameMatcher = STACK_FRAME_PATTERN.matcher(line);
+                if (frameMatcher.matches()) {
+                    String frame = frameMatcher.group(1).trim();
+                    // Clean up frame format
+                    frame = cleanFrame(frame);
+                    if (!frame.isEmpty()) {
+                        currentStack.add(frame);
+                    }
+                }
+            }
+
+            // Don't forget the last stack
+            if (!currentStack.isEmpty()) {
+                String collapsed = collapseStack(currentStack);
+                stackCounts.merge(collapsed, 1L, Long::sum);
+            }
+        }
+
+        // Output in collapsed format
+        for (Map.Entry<String, Long> entry : stackCounts.entrySet()) {
+            out.println(entry.getKey() + " " + entry.getValue());
+        }
+    }
+
+    private static String collapseStack(List<String> stack) {
+        // Reverse because JFR shows leaf first, but flame graphs want root 
first
+        StringBuilder sb = new StringBuilder();
+        for (int i = stack.size() - 1; i >= 0; i--) {
+            if (sb.length() > 0) {
+                sb.append(";");
+            }
+            sb.append(stack.get(i));
+        }
+        return sb.toString();
+    }
+
+    private static String cleanFrame(String frame) {
+        // Remove line numbers and source file info
+        frame = frame.replaceAll("\\(.*\\)", "");
+        // Remove module prefixes like java.base/
+        frame = frame.replaceAll("^[\\w.]+/", "");
+        return frame.trim();
+    }
+
+    /**
+     * Filter stacks to only those matching a pattern.
+     */
+    public static void filterStacks(String inputFile, String pattern, 
PrintStream out) throws IOException {
+        Pattern p = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
+
+        try (BufferedReader reader = 
Files.newBufferedReader(Paths.get(inputFile))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                if (p.matcher(line).find()) {
+                    out.println(line);
+                }
+            }
+        }
+    }
+
+    /**
+     * Show top methods by sample count.
+     */
+    public static void topMethods(String inputFile, int n, PrintStream out) 
throws IOException {
+        Map<String, Long> methodCounts = new HashMap<>();
+
+        try (BufferedReader reader = 
Files.newBufferedReader(Paths.get(inputFile))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                int lastSpace = line.lastIndexOf(' ');
+                if (lastSpace < 0) continue;
+
+                String stackPart = line.substring(0, lastSpace);
+                long count = Long.parseLong(line.substring(lastSpace + 
1).trim());
+
+                // Extract individual methods from stack
+                String[] frames = stackPart.split(";");
+                for (String frame : frames) {
+                    methodCounts.merge(frame, count, Long::sum);
+                }
+            }
+        }
+
+        // Sort by count descending
+        List<Map.Entry<String, Long>> sorted = new 
ArrayList<>(methodCounts.entrySet());
+        sorted.sort((a, b) -> Long.compare(b.getValue(), a.getValue()));
+
+        out.println("Top " + n + " methods by sample count:");
+        out.println("====================================");
+        int shown = 0;
+        for (Map.Entry<String, Long> entry : sorted) {
+            if (shown >= n) break;
+            out.printf("%8d  %s%n", entry.getValue(), entry.getKey());
+            shown++;
+        }
+    }
+
+    /**
+     * Compare two profiles and show differences.
+     */
+    public static void diffProfiles(String baselineFile, String currentFile, 
PrintStream out) throws IOException {
+        Map<String, Long> baseline = loadCollapsed(baselineFile);
+        Map<String, Long> current = loadCollapsed(currentFile);
+
+        // Find methods with biggest changes
+        Map<String, Double> changes = new HashMap<>();
+
+        Set<String> allMethods = new HashSet<>();
+        allMethods.addAll(baseline.keySet());
+        allMethods.addAll(current.keySet());
+
+        long baselineTotal = 
baseline.values().stream().mapToLong(Long::longValue).sum();
+        long currentTotal = 
current.values().stream().mapToLong(Long::longValue).sum();
+
+        for (String method : allMethods) {
+            long baseCount = baseline.getOrDefault(method, 0L);
+            long currCount = current.getOrDefault(method, 0L);
+
+            double basePct = baselineTotal > 0 ? (baseCount * 100.0 / 
baselineTotal) : 0;
+            double currPct = currentTotal > 0 ? (currCount * 100.0 / 
currentTotal) : 0;
+
+            double diff = currPct - basePct;
+            if (Math.abs(diff) > 0.1) { // Only show significant changes
+                changes.put(method, diff);
+            }
+        }
+
+        // Sort by absolute change
+        List<Map.Entry<String, Double>> sorted = new 
ArrayList<>(changes.entrySet());
+        sorted.sort((a, b) -> Double.compare(Math.abs(b.getValue()), 
Math.abs(a.getValue())));
+
+        out.println("Profile Comparison (positive = regression, negative = 
improvement)");
+        
out.println("================================================================");
+        out.printf("Baseline total samples: %d%n", baselineTotal);
+        out.printf("Current total samples: %d%n", currentTotal);
+        out.println();
+
+        int shown = 0;
+        for (Map.Entry<String, Double> entry : sorted) {
+            if (shown >= 30) break;
+            String sign = entry.getValue() > 0 ? "+" : "";
+            out.printf("%s%.2f%%  %s%n", sign, entry.getValue(), 
entry.getKey());
+            shown++;
+        }
+    }
+
+    private static Map<String, Long> loadCollapsed(String file) throws 
IOException {
+        Map<String, Long> methodCounts = new HashMap<>();
+
+        try (BufferedReader reader = Files.newBufferedReader(Paths.get(file))) 
{
+            String line;
+            while ((line = reader.readLine()) != null) {
+                int lastSpace = line.lastIndexOf(' ');
+                if (lastSpace < 0) continue;
+
+                String stackPart = line.substring(0, lastSpace);
+                long count = Long.parseLong(line.substring(lastSpace + 
1).trim());
+
+                String[] frames = stackPart.split(";");
+                for (String frame : frames) {
+                    methodCounts.merge(frame, count, Long::sum);
+                }
+            }
+        }
+
+        return methodCounts;
+    }
+
+    /**
+     * Programmatic API for generating collapsed stacks from JFR.
+     */
+    public static Map<String, Long> collapseFromJfr(Path jfrTextFile) throws 
IOException {
+        Map<String, Long> stackCounts = new LinkedHashMap<>();
+        List<String> currentStack = new ArrayList<>();
+
+        try (BufferedReader reader = Files.newBufferedReader(jfrTextFile)) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                Matcher eventMatcher = EVENT_SEPARATOR_PATTERN.matcher(line);
+                if (eventMatcher.find()) {
+                    if (!currentStack.isEmpty()) {
+                        String collapsed = collapseStack(currentStack);
+                        stackCounts.merge(collapsed, 1L, Long::sum);
+                        currentStack.clear();
+                    }
+                    continue;
+                }
+
+                Matcher frameMatcher = STACK_FRAME_PATTERN.matcher(line);
+                if (frameMatcher.matches()) {
+                    String frame = cleanFrame(frameMatcher.group(1).trim());
+                    if (!frame.isEmpty()) {
+                        currentStack.add(frame);
+                    }
+                }
+            }
+
+            if (!currentStack.isEmpty()) {
+                String collapsed = collapseStack(currentStack);
+                stackCounts.merge(collapsed, 1L, Long::sum);
+            }
+        }
+
+        return stackCounts;
+    }
+
+    /**
+     * Filter collapsed stacks programmatically.
+     */
+    public static Map<String, Long> filterByPattern(Map<String, Long> stacks, 
String pattern) {
+        Pattern p = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
+        Map<String, Long> filtered = new LinkedHashMap<>();
+
+        for (Map.Entry<String, Long> entry : stacks.entrySet()) {
+            if (p.matcher(entry.getKey()).find()) {
+                filtered.put(entry.getKey(), entry.getValue());
+            }
+        }
+
+        return filtered;
+    }
+
+    /**
+     * Get top N methods from collapsed stacks.
+     */
+    public static List<Map.Entry<String, Long>> getTopMethods(Map<String, 
Long> stacks, int n) {
+        Map<String, Long> methodCounts = new HashMap<>();
+
+        for (Map.Entry<String, Long> entry : stacks.entrySet()) {
+            String[] frames = entry.getKey().split(";");
+            for (String frame : frames) {
+                methodCounts.merge(frame, entry.getValue(), Long::sum);
+            }
+        }
+
+        List<Map.Entry<String, Long>> sorted = new 
ArrayList<>(methodCounts.entrySet());
+        sorted.sort((a, b) -> Long.compare(b.getValue(), a.getValue()));
+
+        return sorted.subList(0, Math.min(n, sorted.size()));
+    }
+}
diff --git 
a/subprojects/performance/src/jmh/resources/workloads/cold-startup.json 
b/subprojects/performance/src/jmh/resources/workloads/cold-startup.json
new file mode 100644
index 0000000000..8247630114
--- /dev/null
+++ b/subprojects/performance/src/jmh/resources/workloads/cold-startup.json
@@ -0,0 +1,42 @@
+{
+  "name": "Cold Startup Workload",
+  "description": "Measures performance of first-time method invocations before 
callsite caches warm up",
+  "benchmarks": [
+    {
+      "pattern": ".*cold.*",
+      "weight": 0.40,
+      "description": "Cold callsite benchmarks"
+    },
+    {
+      "pattern": ".*FirstCall.*",
+      "weight": 0.30,
+      "description": "First method call measurements"
+    },
+    {
+      "pattern": "MemoryAllocationBench.*createAndCallOnce",
+      "weight": 0.30,
+      "description": "Create-call-discard patterns"
+    }
+  ],
+  "parameters": {
+    "callsiteWarmup": false,
+    "measureFirstInvocation": true
+  },
+  "jvmArgs": [
+    "-Xms128m",
+    "-Xmx128m",
+    "-XX:+UseG1GC",
+    "-XX:TieredStopAtLevel=1"
+  ],
+  "jmhArgs": {
+    "warmupIterations": 0,
+    "measurementIterations": 10,
+    "forks": 3,
+    "mode": "SingleShotTime",
+    "timeUnit": "MICROSECONDS"
+  },
+  "analysis": {
+    "expectedPattern": "Single-shot times should be significantly higher than 
warmed-up throughput benchmarks",
+    "metrics": ["singleShot.time", "allocation_per_operation"]
+  }
+}
diff --git 
a/subprojects/performance/src/jmh/resources/workloads/dispatch-stress.json 
b/subprojects/performance/src/jmh/resources/workloads/dispatch-stress.json
new file mode 100644
index 0000000000..04f4f7eaca
--- /dev/null
+++ b/subprojects/performance/src/jmh/resources/workloads/dispatch-stress.json
@@ -0,0 +1,41 @@
+{
+  "name": "Dispatch Stress Test",
+  "description": "Stress test focusing on method dispatch mechanisms - 
polymorphic dispatch, interface dispatch, and mixed-type collections",
+  "benchmarks": [
+    {
+      "pattern": "MixedTypeCollectionBench",
+      "weight": 0.35,
+      "description": "Collections with varying type diversity (2-20 types)"
+    },
+    {
+      "pattern": "InterfaceDispatchBench",
+      "weight": 0.35,
+      "description": "Interface-based polymorphic dispatch"
+    },
+    {
+      "pattern": "PolymorphicDispatchBench",
+      "weight": 0.30,
+      "description": "General polymorphic dispatch scenarios"
+    }
+  ],
+  "parameters": {
+    "typeCount": [2, 5, 10, 20],
+    "collectionSize": 100,
+    "callsiteCount": 1000
+  },
+  "jvmArgs": [
+    "-Xms256m",
+    "-Xmx256m",
+    "-XX:+UseG1GC"
+  ],
+  "jmhArgs": {
+    "warmupIterations": 5,
+    "measurementIterations": 5,
+    "forks": 2,
+    "timeUnit": "MICROSECONDS"
+  },
+  "analysis": {
+    "expectedPattern": "Performance should degrade as type count increases 
beyond callsite cache size (typically 4-8)",
+    "metrics": ["throughput", "allocation_rate"]
+  }
+}
diff --git 
a/subprojects/performance/src/jmh/resources/workloads/grails-typical.json 
b/subprojects/performance/src/jmh/resources/workloads/grails-typical.json
new file mode 100644
index 0000000000..460dc69c60
--- /dev/null
+++ b/subprojects/performance/src/jmh/resources/workloads/grails-typical.json
@@ -0,0 +1,48 @@
+{
+  "name": "Grails Typical Web Application",
+  "description": "Simulates a typical Grails web application workload with 
CRUD operations, dynamic finders, and template rendering",
+  "benchmarks": [
+    {
+      "pattern": "RequestLifecycleBench",
+      "weight": 0.30,
+      "description": "Controller request handling lifecycle"
+    },
+    {
+      "pattern": "TemplateRenderBench",
+      "weight": 0.25,
+      "description": "GSP-like view rendering"
+    },
+    {
+      "pattern": "DynamicFinderBench",
+      "weight": 0.20,
+      "description": "GORM dynamic finder methods"
+    },
+    {
+      "pattern": "CollectionOperationsBench",
+      "weight": 0.15,
+      "description": "Groovy collection operations on domain objects"
+    },
+    {
+      "pattern": "EntityTraversalBench",
+      "weight": 0.10,
+      "description": "Traversing domain object relationships"
+    }
+  ],
+  "parameters": {
+    "domainObjectCount": 1000,
+    "collectionSize": 100,
+    "templateComplexity": "medium",
+    "requestsPerSession": 50
+  },
+  "jvmArgs": [
+    "-Xms512m",
+    "-Xmx512m",
+    "-XX:+UseG1GC"
+  ],
+  "jmhArgs": {
+    "warmupIterations": 5,
+    "measurementIterations": 5,
+    "forks": 2,
+    "timeUnit": "MILLISECONDS"
+  }
+}
diff --git 
a/subprojects/performance/src/jmh/resources/workloads/memory-intensive.json 
b/subprojects/performance/src/jmh/resources/workloads/memory-intensive.json
new file mode 100644
index 0000000000..a026efa5e8
--- /dev/null
+++ b/subprojects/performance/src/jmh/resources/workloads/memory-intensive.json
@@ -0,0 +1,44 @@
+{
+  "name": "Memory Intensive Workload",
+  "description": "Focuses on memory allocation patterns and callsite cache 
memory overhead",
+  "benchmarks": [
+    {
+      "pattern": "MemoryAllocationBench",
+      "weight": 0.30,
+      "description": "Heap allocation during Groovy operations"
+    },
+    {
+      "pattern": "CallsiteGrowthBench",
+      "weight": 0.35,
+      "description": "Memory cost of accumulating callsites"
+    },
+    {
+      "pattern": "LongRunningSessionBench",
+      "weight": 0.35,
+      "description": "Memory patterns during sustained load"
+    }
+  ],
+  "parameters": {
+    "callsiteCount": [100, 1000, 10000],
+    "sessionDuration": "extended",
+    "gcMonitoring": true
+  },
+  "jvmArgs": [
+    "-Xms256m",
+    "-Xmx256m",
+    "-XX:+UseG1GC",
+    "-XX:+PrintGCDetails",
+    "-Xlog:gc*:file=gc.log:time,uptime:filecount=5,filesize=10M"
+  ],
+  "jmhArgs": {
+    "warmupIterations": 3,
+    "measurementIterations": 5,
+    "forks": 1,
+    "timeUnit": "MILLISECONDS",
+    "profilers": ["gc"]
+  },
+  "analysis": {
+    "expectedPattern": "Memory should grow proportionally with callsite count; 
watch for retention issues",
+    "metrics": ["gc.alloc.rate", "gc.churn.PS_Eden_Space", "gc.count"]
+  }
+}


Reply via email to