Copilot commented on code in PR #2381:
URL: https://github.com/apache/groovy/pull/2381#discussion_r2810120355


##########
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)
+        }

Review Comment:
   In `benchmarkClosureMultiParams`, the closure variable is named `c` and one 
of the closure parameters is also named `c` (`{ a, b, c -> ... }`). This 
parameter name shadows the outer `c` variable and makes the code harder to 
read/maintain. Rename the parameter (e.g., `c3`/`z`) or rename the closure 
variable to avoid the collision.



##########
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

Review Comment:
   `fieldReadWrite` is documented as “Read/write a public field”, but `int 
instanceField = 42` in Groovy defines a property (with an underlying private 
field + accessors). If you specifically want to benchmark direct field access, 
consider using the direct-field operator (`this.@instanceField`) and/or adjust 
the comment to reflect Groovy property/field semantics.



##########
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']
+        }

Review Comment:
   The Javadoc for `mapStyleAccess` says it “tests Groovy's map-like property 
access on a POGO”, but the code is operating on an actual `Map` and using 
bracket notation (`map['a']`). Either update the comment to describe Map 
bracket access, or change the benchmark to the intended POGO-style access.



##########
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()) }

Review Comment:
   `originalEachToString` allocates a new list (`[1]`) and a new closure on 
every loop iteration. That allocation/GC work will dominate the measurement and 
makes it difficult to interpret results as “closure dispatch + toString cost”. 
If the goal is to measure dispatch, hoist the list and/or closure out of the 
inner loop (e.g., create them once per benchmark invocation or in a `@Setup`).
   ```suggestion
           List<Integer> list = [1]
           Closure<?> c = { bh.consume(it.toString()) }
           for (int i = 0; i < LOOP_COUNT; i++) {
               list.each(c)
   ```



##########
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) }

Review Comment:
   `eachIdentity` also creates a new list (`[1]`) and a new closure for every 
loop iteration, so this benchmark will largely measure allocation/GC rather 
than the steady-state `each`/closure invocation overhead. Consider reusing the 
list/closure outside the loop (similar to the intent described in the comment 
for isolating dispatch).
   ```suggestion
           def list = [1]
           def identityClosure = { bh.consume(it) }
           for (int i = 0; i < LOOP_COUNT; i++) {
               list.each(identityClosure)
   ```



##########
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)

Review Comment:
   `integerMultiply` is described as measuring `Integer.multiply(Integer)`, but 
the loop body overwrites `product` with `(i % 100) * (i % 50)` each iteration. 
This mixes in `%` operations and doesn't use the prior `product`, so it's not 
really measuring repeated multiplication cost in a tight loop. Consider 
simplifying to a single multiply (and/or accumulating with `product *= ...`) so 
the benchmark matches its intent.
   ```suggestion
               product *= i
   ```



##########
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")
+            })
+        }

Review Comment:
   In `tapScope`, the benchmark creates a new `Person` (and `Address`) on every 
iteration and then calls `tap { ... }`. The allocation work is likely to 
dominate, making it hard to isolate the invokedynamic overhead of `tap` itself. 
Consider reusing a preallocated `Person` from state (resetting fields inside 
`tap`), or splitting allocation into a separate baseline benchmark for 
comparison.



##########
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()

Review Comment:
   `chainedPropertyAccess` is described as “Chained property access”, but the 
expression being benchmarked is `list.first().length()`, which is a chain of 
method calls (`first()` and `length()`), not property resolution. Consider 
switching to an actual chained property expression (or renaming/updating the 
comment) so the benchmark matches its stated purpose.
   ```suggestion
           Map<String, Object> root = [level1: [level2: [value: 0]]]
           int sum = 0
           for (int i = 0; i < ITERATIONS; i++) {
               root.level1.level2.value = i
               sum += root.level1.level2.value
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to