You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2026/02/16 00:19:31 UTC

(groovy) branch master updated (9b4ec311df -> 61dae3df54)

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

paulk pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


    from 9b4ec311df GROOVY-9805: checkcast to keep local variable type in the stackmap frame
     new 8024cfbaa8 GROOVY-10307: add JMH benchmarks for invokedynamic performance patterns
     new 61dae3df54 GROOVY-10307: address review feedback

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../org/apache/groovy/perf/ClosureBench.groovy     | 309 +++++++++++++++++++++
 .../org/apache/groovy/perf/GStringBench.groovy     | 109 ++++++++
 .../org/apache/groovy/perf/GroovyIdiomBench.groovy | 277 ++++++++++++++++++
 .../org/apache/groovy/perf/LoopsBench.groovy       | 104 +++++++
 .../groovy/perf/MethodInvocationBench.groovy       | 187 +++++++++++++
 .../org/apache/groovy/perf/OperatorBench.groovy    | 207 ++++++++++++++
 .../apache/groovy/perf/PropertyAccessBench.groovy  | 134 +++++++++
 7 files changed, 1327 insertions(+)
 create mode 100644 subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/ClosureBench.groovy
 create mode 100644 subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/GStringBench.groovy
 create mode 100644 subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/GroovyIdiomBench.groovy
 create mode 100644 subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/LoopsBench.groovy
 create mode 100644 subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/MethodInvocationBench.groovy
 create mode 100644 subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/OperatorBench.groovy
 create mode 100644 subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/PropertyAccessBench.groovy


(groovy) 01/02: GROOVY-10307: add JMH benchmarks for invokedynamic performance patterns

Posted by pa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

paulk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 8024cfbaa89092e159b879639d552ff93b6c4767
Author: James Fredley <ja...@users.noreply.github.com>
AuthorDate: Sun Feb 15 18:35:19 2026 -0500

    GROOVY-10307: add JMH benchmarks for invokedynamic performance patterns
---
 .../org/apache/groovy/perf/ClosureBench.groovy     | 309 +++++++++++++++++++++
 .../org/apache/groovy/perf/GStringBench.groovy     | 109 ++++++++
 .../org/apache/groovy/perf/GroovyIdiomBench.groovy | 277 ++++++++++++++++++
 .../org/apache/groovy/perf/LoopsBench.groovy       | 104 +++++++
 .../groovy/perf/MethodInvocationBench.groovy       | 187 +++++++++++++
 .../org/apache/groovy/perf/OperatorBench.groovy    | 206 ++++++++++++++
 .../apache/groovy/perf/PropertyAccessBench.groovy  | 133 +++++++++
 7 files changed, 1325 insertions(+)

diff --git a/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/ClosureBench.groovy b/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/ClosureBench.groovy
new file mode 100644
index 0000000000..4e83c3ad57
--- /dev/null
+++ b/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/ClosureBench.groovy
@@ -0,0 +1,309 @@
+/*
+ *  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.perf
+
+import org.openjdk.jmh.annotations.*
+import org.openjdk.jmh.infra.Blackhole
+
+import java.util.concurrent.TimeUnit
+
+/**
+ * Tests closure performance including creation, reuse, multi-parameter
+ * invocation, variable capture, delegation, nesting, method references,
+ * currying, composition, spread operator, trampoline recursion, and
+ * collection operations (each/collect/findAll/inject).
+ */
+@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Fork(3)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Thread)
+class ClosureBench {
+    static final int ITERATIONS = 1_000_000
+
+    String instanceProperty = "instance"
+
+    /**
+     * Benchmark: Simple closure creation and invocation
+     */
+    @Benchmark
+    void benchmarkSimpleClosureCreation(Blackhole bh) {
+        for (int i = 0; i < ITERATIONS; i++) {
+            Closure c = { it * 2 }
+            bh.consume(c(i))
+        }
+    }
+
+    /**
+     * Benchmark: Reuse same closure (no creation overhead)
+     */
+    @Benchmark
+    void benchmarkClosureReuse(Blackhole bh) {
+        Closure c = { it * 2 }
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += c(i)
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Benchmark: Closure with multiple parameters
+     */
+    @Benchmark
+    void benchmarkClosureMultiParams(Blackhole bh) {
+        Closure c = { a, b, c -> a + b + c }
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += c(i, i + 1, i + 2)
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Benchmark: Closure accessing local variable (captured variable)
+     */
+    @Benchmark
+    void benchmarkClosureWithCapture(Blackhole bh) {
+        int captured = 100
+        Closure c = { it + captured }
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += c(i)
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Benchmark: Closure modifying captured variable
+     */
+    @Benchmark
+    void benchmarkClosureModifyCapture(Blackhole bh) {
+        int counter = 0
+        Closure c = { counter++ }
+        for (int i = 0; i < ITERATIONS; i++) {
+            c()
+        }
+        bh.consume(counter)
+    }
+
+    /**
+     * Benchmark: Closure with owner delegation
+     */
+    @Benchmark
+    void benchmarkClosureDelegation(Blackhole bh) {
+        Closure c = { instanceProperty.length() }
+        c.delegate = this
+        c.resolveStrategy = Closure.DELEGATE_FIRST
+
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += c()
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Benchmark: Nested closures
+     */
+    @Benchmark
+    void benchmarkNestedClosures(Blackhole bh) {
+        Closure outer = { x ->
+            Closure inner = { y -> x + y }
+            inner(x)
+        }
+
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += outer(i)
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Benchmark: Method reference as closure
+     */
+    @Benchmark
+    void benchmarkMethodReference(Blackhole bh) {
+        List<Integer> list = [1, 2, 3, 4, 5]
+        Closure sizeRef = list.&size
+
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += sizeRef()
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Benchmark: Curried closure
+     */
+    @Benchmark
+    void benchmarkCurriedClosure(Blackhole bh) {
+        Closure add = { a, b -> a + b }
+        Closure addFive = add.curry(5)
+
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += addFive(i)
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Benchmark: Right curried closure
+     */
+    @Benchmark
+    void benchmarkRightCurriedClosure(Blackhole bh) {
+        Closure subtract = { a, b -> a - b }
+        Closure subtractFive = subtract.rcurry(5)
+
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += subtractFive(i)
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Benchmark: Closure composition (rightShift >>)
+     */
+    @Benchmark
+    void benchmarkClosureComposition(Blackhole bh) {
+        Closure double_ = { it * 2 }
+        Closure addOne = { it + 1 }
+        Closure composed = double_ >> addOne
+
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += composed(i)
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Benchmark: Closure as method parameter
+     */
+    @Benchmark
+    void benchmarkClosureAsParameter(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += applyOperation(i) { it * 2 }
+        }
+        bh.consume(sum)
+    }
+
+    static int applyOperation(int value, Closure<Integer> operation) {
+        operation(value)
+    }
+
+    /**
+     * Benchmark: Closure with spread operator
+     */
+    @Benchmark
+    void benchmarkClosureSpread(Blackhole bh) {
+        Closure sum3 = { a, b, c -> a + b + c }
+        List<Integer> args = [1, 2, 3]
+
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += sum3(*args)
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Benchmark: Closure call vs doCall
+     */
+    @Benchmark
+    void benchmarkClosureCallMethod(Blackhole bh) {
+        Closure c = { it * 2 }
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += c.call(i)
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Benchmark: Closure with trampoline (for recursion)
+     */
+    @Benchmark
+    void benchmarkClosureTrampoline(Blackhole bh) {
+        Closure factorial
+        factorial = { n, acc = 1G ->
+            n <= 1 ? acc : factorial.trampoline(n - 1, n * acc)
+        }.trampoline()
+
+        // Smaller iteration count due to computation cost
+        for (int i = 0; i < ITERATIONS / 100; i++) {
+            bh.consume(factorial(20))
+        }
+    }
+
+    /**
+     * Benchmark: each with closure (common pattern)
+     */
+    @Benchmark
+    void benchmarkEachWithClosure(Blackhole bh) {
+        List<Integer> list = (1..10).toList()
+        int sum = 0
+        for (int i = 0; i < ITERATIONS / 10; i++) {
+            list.each { sum += it }
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Benchmark: collect with closure
+     */
+    @Benchmark
+    void benchmarkCollectWithClosure(Blackhole bh) {
+        List<Integer> list = (1..10).toList()
+        for (int i = 0; i < ITERATIONS / 10; i++) {
+            bh.consume(list.collect { it * 2 })
+        }
+    }
+
+    /**
+     * Benchmark: findAll with closure
+     */
+    @Benchmark
+    void benchmarkFindAllWithClosure(Blackhole bh) {
+        List<Integer> list = (1..10).toList()
+        for (int i = 0; i < ITERATIONS / 10; i++) {
+            bh.consume(list.findAll { it > 5 })
+        }
+    }
+
+    /**
+     * Benchmark: inject/reduce with closure
+     */
+    @Benchmark
+    void benchmarkInjectWithClosure(Blackhole bh) {
+        List<Integer> list = (1..10).toList()
+        int sum = 0
+        for (int i = 0; i < ITERATIONS / 10; i++) {
+            sum += list.inject(0) { acc, val -> acc + val }
+        }
+        bh.consume(sum)
+    }
+
+}
diff --git a/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/GStringBench.groovy b/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/GStringBench.groovy
new file mode 100644
index 0000000000..ff601ee44f
--- /dev/null
+++ b/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/GStringBench.groovy
@@ -0,0 +1,109 @@
+/*
+ *  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.perf
+
+import org.openjdk.jmh.annotations.*
+import org.openjdk.jmh.infra.Blackhole
+
+import java.util.concurrent.TimeUnit
+
+/**
+ * Tests the performance of GString creation, interpolation, and
+ * calling methods on GString results — including simple and multi-value
+ * interpolation, comparison against plain String concatenation, use as
+ * Map keys, and repeated toString() evaluation.
+ */
+@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Fork(3)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Thread)
+class GStringBench {
+    static final int ITERATIONS = 1_000_000
+
+    /**
+     * Simple GString with one interpolated value and a method call on the result.
+     */
+    @Benchmark
+    void simpleInterpolation(Blackhole bh) {
+        String base = "Hello"
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += "${base}${i}".length()
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Multi-value GString with method call — tests cost of multiple
+     * interpolation expressions and a follow-on method dispatch.
+     */
+    @Benchmark
+    void multiValueInterpolation(Blackhole bh) {
+        String a = "A"
+        String b = "B"
+        for (int i = 0; i < ITERATIONS; i++) {
+            bh.consume("${a}-${i}-${b}".toUpperCase())
+        }
+    }
+
+    /**
+     * GString compared to plain String concatenation — baseline to
+     * isolate the GString-specific overhead.
+     */
+    @Benchmark
+    void stringConcatBaseline(Blackhole bh) {
+        String base = "Hello"
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += (base + i).length()
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * GString used as a Map key — triggers toString() and hashCode(),
+     * testing lazy evaluation and method dispatch on the resulting String.
+     */
+    @Benchmark
+    void gstringAsMapKey(Blackhole bh) {
+        Map<String, Integer> map = [:]
+        String prefix = "key"
+        for (int i = 0; i < ITERATIONS; i++) {
+            map["${prefix}${i % 100}"] = i
+        }
+        bh.consume(map)
+    }
+
+    /**
+     * Repeated toString() on the same GString — tests whether the
+     * GString caches its string representation.
+     */
+    @Benchmark
+    void repeatedToString(Blackhole bh) {
+        String name = "World"
+        GString gs = "Hello ${name}!"
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += gs.toString().length()
+        }
+        bh.consume(sum)
+    }
+}
diff --git a/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/GroovyIdiomBench.groovy b/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/GroovyIdiomBench.groovy
new file mode 100644
index 0000000000..d934b95568
--- /dev/null
+++ b/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/GroovyIdiomBench.groovy
@@ -0,0 +1,277 @@
+/*
+ *  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.perf
+
+import org.openjdk.jmh.annotations.*
+import org.openjdk.jmh.infra.Blackhole
+
+import java.util.concurrent.TimeUnit
+
+/**
+ * Tests performance of Groovy-specific language idioms: safe navigation
+ * (?.), spread-dot (*.), elvis (?:), with/tap scoping, range creation
+ * and iteration, and 'as' type coercion.
+ */
+@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Fork(3)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Thread)
+class GroovyIdiomBench {
+    static final int ITERATIONS = 1_000_000
+
+    // Helper class for safe-nav / spread-dot / with tests
+    static class Person {
+        String name
+        Address address
+    }
+
+    static class Address {
+        String city
+        String zip
+    }
+
+    // Pre-built test data
+    Person personWithAddress
+    Person personNullAddress
+    List<Person> people
+
+    @Setup(Level.Trial)
+    void setup() {
+        personWithAddress = new Person(name: "Alice", address: new Address(city: "Springfield", zip: "62704"))
+        personNullAddress = new Person(name: "Bob", address: null)
+        people = (1..100).collect { new Person(name: "Person$it", address: new Address(city: "City$it", zip: "${10000 + it}")) }
+    }
+
+    // ===== SAFE NAVIGATION (?.) =====
+
+    /**
+     * Safe navigation on non-null chain — obj?.prop?.prop.
+     */
+    @Benchmark
+    void safeNavNonNull(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += personWithAddress?.address?.city?.length() ?: 0
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Safe navigation hitting null — tests the short-circuit path.
+     */
+    @Benchmark
+    void safeNavNull(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += personNullAddress?.address?.city?.length() ?: 0
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Safe navigation vs normal access — baseline for comparison.
+     */
+    @Benchmark
+    void normalNavBaseline(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += personWithAddress.address.city.length()
+        }
+        bh.consume(sum)
+    }
+
+    // ===== SPREAD-DOT (*.) =====
+
+    /**
+     * Spread-dot operator — list*.property collects a property from all elements.
+     */
+    @Benchmark
+    void spreadDotProperty(Blackhole bh) {
+        for (int i = 0; i < ITERATIONS / 100; i++) {
+            bh.consume(people*.name)
+        }
+    }
+
+    /**
+     * Spread-dot with method call — list*.method().
+     */
+    @Benchmark
+    void spreadDotMethod(Blackhole bh) {
+        for (int i = 0; i < ITERATIONS / 100; i++) {
+            bh.consume(people*.getName())
+        }
+    }
+
+    /**
+     * Spread-dot vs collect — baseline comparison.
+     */
+    @Benchmark
+    void collectBaseline(Blackhole bh) {
+        for (int i = 0; i < ITERATIONS / 100; i++) {
+            bh.consume(people.collect { it.name })
+        }
+    }
+
+    // ===== ELVIS (?:) =====
+
+    /**
+     * Elvis operator with non-null value — takes the left side.
+     */
+    @Benchmark
+    void elvisNonNull(Blackhole bh) {
+        String value = "hello"
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += (value ?: "default").length()
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Elvis operator with null value — takes the right side.
+     */
+    @Benchmark
+    void elvisNull(Blackhole bh) {
+        String value = null
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += (value ?: "default").length()
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Elvis with empty string (Groovy truth: empty string is falsy).
+     */
+    @Benchmark
+    void elvisEmptyString(Blackhole bh) {
+        String value = ""
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += (value ?: "default").length()
+        }
+        bh.consume(sum)
+    }
+
+    // ===== WITH / TAP =====
+
+    /**
+     * with {} — executes closure with object as delegate, returns closure result.
+     */
+    @Benchmark
+    void withScope(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += personWithAddress.with {
+                name.length() + address.city.length()
+            }
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * tap {} — executes closure with object as delegate, returns the object.
+     */
+    @Benchmark
+    void tapScope(Blackhole bh) {
+        for (int i = 0; i < ITERATIONS; i++) {
+            bh.consume(new Person().tap {
+                name = "Test"
+                address = new Address(city: "City", zip: "12345")
+            })
+        }
+    }
+
+    // ===== RANGE =====
+
+    /**
+     * Range creation — (1..N) creates an IntRange object.
+     */
+    @Benchmark
+    void rangeCreation(Blackhole bh) {
+        for (int i = 0; i < ITERATIONS; i++) {
+            bh.consume(1..100)
+        }
+    }
+
+    /**
+     * Range iteration with each — (1..N).each { }.
+     */
+    @Benchmark
+    void rangeIteration(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS / 100; i++) {
+            (1..100).each { sum += it }
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Range contains check — (val in range) uses Range.containsWithinBounds.
+     */
+    @Benchmark
+    void rangeContains(Blackhole bh) {
+        def range = 1..1000
+        int count = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            if ((i % 1500) in range) count++
+        }
+        bh.consume(count)
+    }
+
+    // ===== AS TYPE COERCION =====
+
+    /**
+     * 'as' coercion: list as Set.
+     */
+    @Benchmark
+    void asListToSet(Blackhole bh) {
+        List<Integer> list = [1, 2, 3, 4, 5, 1, 2, 3]
+        for (int i = 0; i < ITERATIONS; i++) {
+            bh.consume(list as Set)
+        }
+    }
+
+    /**
+     * 'as' coercion: object as String.
+     */
+    @Benchmark
+    void asToString(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += (i as String).length()
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * 'as' coercion: String to Integer.
+     */
+    @Benchmark
+    void asStringToInteger(Blackhole bh) {
+        String[] values = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += values[i % 10] as Integer
+        }
+        bh.consume(sum)
+    }
+}
diff --git a/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/LoopsBench.groovy b/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/LoopsBench.groovy
new file mode 100644
index 0000000000..1a26324574
--- /dev/null
+++ b/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/LoopsBench.groovy
@@ -0,0 +1,104 @@
+/*
+ *  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.perf
+
+import org.openjdk.jmh.annotations.*
+import org.openjdk.jmh.infra.Blackhole
+
+import java.util.concurrent.TimeUnit
+
+/**
+ * Tests the overhead of repeated closure and method invocation within
+ * tight loops. Focuses on loop-specific patterns: closure-in-loop vs
+ * method-in-loop, nested iteration, and minimal vs complex loop bodies.
+ *
+ * Collection operation benchmarks (each/collect/findAll/inject on lists)
+ * are in {@link ClosureBench}.
+ */
+@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Fork(3)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Thread)
+class LoopsBench {
+    static final int LOOP_COUNT = 1_000_000
+
+    /**
+     * Loop with [1].each and toString() — exercises closure dispatch
+     * and virtual method call on each iteration.
+     */
+    @Benchmark
+    void originalEachToString(Blackhole bh) {
+        for (int i = 0; i < LOOP_COUNT; i++) {
+            [1].each { bh.consume(it.toString()) }
+        }
+    }
+
+    /**
+     * Minimal each loop — isolates closure dispatch overhead from toString() cost.
+     */
+    @Benchmark
+    void eachIdentity(Blackhole bh) {
+        for (int i = 0; i < LOOP_COUNT; i++) {
+            [1].each { bh.consume(it) }
+        }
+    }
+
+    /**
+     * Reused closure invoked in a loop via .call() — tests call site caching
+     * when the same closure is called repeatedly (no new closure allocation per iteration).
+     */
+    @Benchmark
+    void reusedClosureInLoop(Blackhole bh) {
+        Closure<?> c = { it.toString() }
+        for (int i = 0; i < LOOP_COUNT; i++) {
+            bh.consume(c.call(1))
+        }
+    }
+
+    /**
+     * Direct method call in a loop — baseline comparison against closure dispatch.
+     * Shows the overhead of closure invocation vs plain method invocation.
+     */
+    @Benchmark
+    void methodCallInLoop(Blackhole bh) {
+        for (int i = 0; i < LOOP_COUNT; i++) {
+            bh.consume(doSomething(1))
+        }
+    }
+
+    static String doSomething(Object o) {
+        o.toString()
+    }
+
+    /**
+     * Nested loops with closures — tests call site behavior when multiple
+     * closure call sites are active across nested iteration scopes.
+     */
+    @Benchmark
+    void nestedLoopsWithClosure(Blackhole bh) {
+        int count = (int) Math.sqrt(LOOP_COUNT)
+        for (int i = 0; i < count; i++) {
+            for (int j = 0; j < count; j++) {
+                [i, j].each { bh.consume(it.toString()) }
+            }
+        }
+    }
+}
diff --git a/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/MethodInvocationBench.groovy b/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/MethodInvocationBench.groovy
new file mode 100644
index 0000000000..155094665f
--- /dev/null
+++ b/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/MethodInvocationBench.groovy
@@ -0,0 +1,187 @@
+/*
+ *  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.perf
+
+import org.openjdk.jmh.annotations.*
+import org.openjdk.jmh.infra.Blackhole
+
+import java.util.concurrent.TimeUnit
+
+/**
+ * Tests the overhead of dynamic method invocation and dispatch in Groovy.
+ * Covers instance and static method calls, parameter passing, overloaded
+ * method resolution, monomorphic vs polymorphic call sites, interface
+ * dispatch, and dynamically-typed dispatch.
+ *
+ * Property access is in {@link PropertyAccessBench}.
+ * GString operations are in {@link GStringBench}.
+ * Method references as closures are in {@link ClosureBench}.
+ */
+@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Fork(3)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Thread)
+class MethodInvocationBench {
+    static final int ITERATIONS = 1_000_000
+
+    // Simple fields for method tests
+    int instanceField = 42
+    static int staticField = 100
+
+    // Simple instance method
+    int simpleMethod() {
+        instanceField
+    }
+
+    // Method with parameters
+    int methodWithParams(int a, int b) {
+        a + b
+    }
+
+    // Method with object parameter
+    String methodWithObject(Object obj) {
+        obj.toString()
+    }
+
+    // Overloaded methods to test dispatch
+    String overloaded(String s) { "String: $s" }
+    String overloaded(Integer i) { "Integer: $i" }
+    String overloaded(Object o) { "Object: $o" }
+
+    // Static methods
+    static int staticMethod() {
+        staticField
+    }
+
+    static int staticMethodWithParams(int a, int b) {
+        a + b
+    }
+
+    /**
+     * Benchmark: Simple instance method calls
+     */
+    @Benchmark
+    void benchmarkSimpleMethodCalls(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += simpleMethod()
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Benchmark: Method calls with parameters
+     */
+    @Benchmark
+    void benchmarkMethodWithParams(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += methodWithParams(i, 1)
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Benchmark: Method calls with object parameter
+     */
+    @Benchmark
+    void benchmarkMethodWithObject(Blackhole bh) {
+        for (int i = 0; i < ITERATIONS; i++) {
+            bh.consume(methodWithObject(i))
+        }
+    }
+
+    /**
+     * Benchmark: Static method calls
+     */
+    @Benchmark
+    void benchmarkStaticMethodCalls(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += staticMethod()
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Benchmark: Static method calls with parameters
+     */
+    @Benchmark
+    void benchmarkStaticMethodWithParams(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += staticMethodWithParams(i, 1)
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Benchmark: Monomorphic call site (same type every time)
+     * This should be fast with proper indy optimization
+     */
+    @Benchmark
+    void benchmarkMonomorphicCallSite(Blackhole bh) {
+        String s = "test"
+        for (int i = 0; i < ITERATIONS; i++) {
+            bh.consume(overloaded(s))
+        }
+    }
+
+    /**
+     * Benchmark: Polymorphic call site (different types)
+     * This tests the call site cache effectiveness
+     */
+    @Benchmark
+    void benchmarkPolymorphicCallSite(Blackhole bh) {
+        Object[] args = ["string", 42, new Object(), "another", 100, [1, 2, 3]]
+
+        for (int i = 0; i < ITERATIONS; i++) {
+            bh.consume(overloaded(args[i % args.length]))
+        }
+    }
+
+    /**
+     * Benchmark: Method calls through interface
+     */
+    @Benchmark
+    void benchmarkInterfaceMethodCalls(Blackhole bh) {
+        List<Integer> list = [1, 2, 3, 4, 5]
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += list.size()
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Benchmark: Method calls on dynamically typed variable
+     */
+    @Benchmark
+    void benchmarkDynamicTypedCalls(Blackhole bh) {
+        def instance = this
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += instance.simpleMethod()
+        }
+        bh.consume(sum)
+    }
+
+}
diff --git a/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/OperatorBench.groovy b/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/OperatorBench.groovy
new file mode 100644
index 0000000000..1484aa9b46
--- /dev/null
+++ b/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/OperatorBench.groovy
@@ -0,0 +1,206 @@
+/*
+ *  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.perf
+
+import org.openjdk.jmh.annotations.*
+import org.openjdk.jmh.infra.Blackhole
+
+import java.util.concurrent.TimeUnit
+
+/**
+ * Tests the performance of Groovy operator overloading. In Groovy every
+ * operator (+, -, *, /, [], <<, ==, <=>) compiles to a method call
+ * (plus, minus, multiply, div, getAt, leftShift, equals, compareTo)
+ * dispatched through invokedynamic.
+ */
+@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Fork(3)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Thread)
+class OperatorBench {
+    static final int ITERATIONS = 1_000_000
+
+    /**
+     * Integer addition — dispatches to Integer.plus(Integer).
+     */
+    @Benchmark
+    void integerPlus(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum = sum + i
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Integer multiplication — dispatches to Integer.multiply(Integer).
+     */
+    @Benchmark
+    void integerMultiply(Blackhole bh) {
+        int product = 1
+        for (int i = 1; i < ITERATIONS; i++) {
+            product = (i % 100) * (i % 50)
+        }
+        bh.consume(product)
+    }
+
+    /**
+     * BigDecimal arithmetic — common in financial/Grails apps,
+     * all operations go through operator method dispatch.
+     */
+    @Benchmark
+    void bigDecimalArithmetic(Blackhole bh) {
+        BigDecimal sum = 0.0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum = sum + 1.5
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * String multiply (repeat) — "abc" * 3 dispatches to String.multiply(Integer).
+     */
+    @Benchmark
+    void stringMultiply(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += ("x" * 5).length()
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * List subscript read — list[i] dispatches to List.getAt(int).
+     */
+    @Benchmark
+    void listGetAt(Blackhole bh) {
+        List<Integer> list = (0..99).toList()
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += list[i % 100]
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * List subscript write — list[i] = val dispatches to List.putAt(int, Object).
+     */
+    @Benchmark
+    void listPutAt(Blackhole bh) {
+        List<Integer> list = (0..99).toList()
+        for (int i = 0; i < ITERATIONS; i++) {
+            list[i % 100] = i
+        }
+        bh.consume(list)
+    }
+
+    /**
+     * Map subscript read/write — map[key] dispatches to getAt/putAt.
+     */
+    @Benchmark
+    void mapGetAtPutAt(Blackhole bh) {
+        Map<String, Integer> map = [a: 1, b: 2, c: 3, d: 4, e: 5]
+        String[] keys = ['a', 'b', 'c', 'd', 'e']
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            map[keys[i % 5]] = i
+            sum += map[keys[i % 5]]
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Left shift operator — list << item dispatches to List.leftShift(Object).
+     */
+    @Benchmark
+    void listLeftShift(Blackhole bh) {
+        List<Integer> list = []
+        for (int i = 0; i < ITERATIONS; i++) {
+            if (i % 1000 == 0) list = []
+            list << i
+        }
+        bh.consume(list)
+    }
+
+    /**
+     * Equals operator — == dispatches to Object.equals(Object) in Groovy
+     * (not reference equality like Java).
+     */
+    @Benchmark
+    void equalsOperator(Blackhole bh) {
+        String a = "hello"
+        String b = "hello"
+        int count = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            if (a == b) count++
+        }
+        bh.consume(count)
+    }
+
+    /**
+     * Spaceship operator — <=> dispatches to Comparable.compareTo().
+     */
+    @Benchmark
+    void spaceshipOperator(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += (i <=> (i + 1))
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Comparison operators — <, >, <=, >= dispatch through compareTo().
+     */
+    @Benchmark
+    void comparisonOperators(Blackhole bh) {
+        int count = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            if (i > 0 && i < ITERATIONS && i >= 0 && i <= ITERATIONS) count++
+        }
+        bh.consume(count)
+    }
+
+    /**
+     * Unary minus — dispatches to Number.unaryMinus().
+     */
+    @Benchmark
+    void unaryMinus(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += (-i)
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * In operator — (item in collection) dispatches to Collection.isCase(Object).
+     */
+    @Benchmark
+    void inOperator(Blackhole bh) {
+        List<Integer> list = (0..99).toList()
+        int count = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            if ((i % 100) in list) count++
+        }
+        bh.consume(count)
+    }
+}
diff --git a/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/PropertyAccessBench.groovy b/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/PropertyAccessBench.groovy
new file mode 100644
index 0000000000..0b45b44384
--- /dev/null
+++ b/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/PropertyAccessBench.groovy
@@ -0,0 +1,133 @@
+/*
+ *  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.perf
+
+import org.openjdk.jmh.annotations.*
+import org.openjdk.jmh.infra.Blackhole
+
+import java.util.concurrent.TimeUnit
+
+/**
+ * Tests the performance of Groovy property access patterns including
+ * field read/write, getter/setter dispatch, dynamically-typed property
+ * access, map bracket and dot-property notation, and chained property
+ * resolution.
+ */
+@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Fork(3)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Thread)
+class PropertyAccessBench {
+    static final int ITERATIONS = 1_000_000
+
+    int instanceField = 42
+    String stringProperty = "hello"
+
+    // Explicit getter/setter for comparison
+    private int _backingField = 10
+    int getBackingField() { _backingField }
+    void setBackingField(int value) { _backingField = value }
+
+    /**
+     * Read/write a public field — the simplest property access path.
+     */
+    @Benchmark
+    void fieldReadWrite(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            instanceField = i
+            sum += instanceField
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Read/write through explicit getter/setter methods —
+     * tests the overhead of Groovy's property-to-getter/setter dispatch.
+     */
+    @Benchmark
+    void getterSetterAccess(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            backingField = i
+            sum += backingField
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Property access on a dynamically typed variable —
+     * tests the cost when the compiler cannot statically resolve the property.
+     */
+    @Benchmark
+    void dynamicTypedPropertyAccess(Blackhole bh) {
+        def obj = this
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            obj.instanceField = i
+            sum += obj.instanceField
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Map-style property access using bracket notation —
+     * tests Groovy's map-like property access on a POGO.
+     */
+    @Benchmark
+    void mapStyleAccess(Blackhole bh) {
+        Map<String, Integer> map = [a: 1, b: 2, c: 3]
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            map['a'] = i
+            sum += map['a']
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Dot-property access on a Map — Groovy allows map.key syntax.
+     */
+    @Benchmark
+    void mapDotPropertyAccess(Blackhole bh) {
+        Map<String, Integer> map = [a: 1, b: 2, c: 3]
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            map.a = i
+            sum += map.a
+        }
+        bh.consume(sum)
+    }
+
+    /**
+     * Chained property access — tests multiple property resolutions
+     * in a single expression.
+     */
+    @Benchmark
+    void chainedPropertyAccess(Blackhole bh) {
+        List<String> list = ["hello", "world"]
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            sum += list.first().length()
+        }
+        bh.consume(sum)
+    }
+}


(groovy) 02/02: GROOVY-10307: address review feedback

Posted by pa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

paulk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 61dae3df5433c7eb0c0442baf5fce2b917220232
Author: James Fredley <ja...@users.noreply.github.com>
AuthorDate: Sun Feb 15 18:46:49 2026 -0500

    GROOVY-10307: address review feedback
---
 .../jmh/groovy/org/apache/groovy/perf/ClosureBench.groovy   |  2 +-
 .../jmh/groovy/org/apache/groovy/perf/OperatorBench.groovy  |  1 +
 .../org/apache/groovy/perf/PropertyAccessBench.groovy       | 13 +++++++------
 3 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/ClosureBench.groovy b/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/ClosureBench.groovy
index 4e83c3ad57..654dde3c57 100644
--- a/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/ClosureBench.groovy
+++ b/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/ClosureBench.groovy
@@ -69,7 +69,7 @@ class ClosureBench {
      */
     @Benchmark
     void benchmarkClosureMultiParams(Blackhole bh) {
-        Closure c = { a, b, c -> a + b + c }
+        Closure c = { a, b, x -> a + b + x }
         int sum = 0
         for (int i = 0; i < ITERATIONS; i++) {
             sum += c(i, i + 1, i + 2)
diff --git a/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/OperatorBench.groovy b/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/OperatorBench.groovy
index 1484aa9b46..349f081ef4 100644
--- a/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/OperatorBench.groovy
+++ b/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/OperatorBench.groovy
@@ -52,6 +52,7 @@ class OperatorBench {
 
     /**
      * Integer multiplication — dispatches to Integer.multiply(Integer).
+     * Uses modulo to keep operands small and avoid overflow to zero.
      */
     @Benchmark
     void integerMultiply(Blackhole bh) {
diff --git a/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/PropertyAccessBench.groovy b/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/PropertyAccessBench.groovy
index 0b45b44384..8ac15e5c0a 100644
--- a/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/PropertyAccessBench.groovy
+++ b/subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/PropertyAccessBench.groovy
@@ -47,7 +47,8 @@ class PropertyAccessBench {
     void setBackingField(int value) { _backingField = value }
 
     /**
-     * Read/write a public field — the simplest property access path.
+     * Read/write a Groovy property — in Groovy, {@code int instanceField}
+     * declares a property backed by a private field with generated getter/setter.
      */
     @Benchmark
     void fieldReadWrite(Blackhole bh) {
@@ -89,8 +90,8 @@ class PropertyAccessBench {
     }
 
     /**
-     * Map-style property access using bracket notation —
-     * tests Groovy's map-like property access on a POGO.
+     * Map bracket notation — {@code map['key']} dispatches to
+     * {@code Map.getAt()} / {@code Map.putAt()}.
      */
     @Benchmark
     void mapStyleAccess(Blackhole bh) {
@@ -119,14 +120,14 @@ class PropertyAccessBench {
 
     /**
      * Chained property access — tests multiple property resolutions
-     * in a single expression.
+     * in a single expression using nested maps.
      */
     @Benchmark
     void chainedPropertyAccess(Blackhole bh) {
-        List<String> list = ["hello", "world"]
+        Map<String, Object> root = [level1: [level2: [value: 42]]]
         int sum = 0
         for (int i = 0; i < ITERATIONS; i++) {
-            sum += list.first().length()
+            sum += root.level1.level2.value
         }
         bh.consume(sum)
     }