[ 
https://issues.apache.org/jira/browse/GROOVY-10307?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18058773#comment-18058773
 ] 

ASF GitHub Bot commented on GROOVY-10307:
-----------------------------------------

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





> Groovy 4 runtime performance on average 2.4x slower than Groovy 3
> -----------------------------------------------------------------
>
>                 Key: GROOVY-10307
>                 URL: https://issues.apache.org/jira/browse/GROOVY-10307
>             Project: Groovy
>          Issue Type: Bug
>          Components: bytecode, performance
>    Affects Versions: 4.0.0-beta-1, 3.0.9
>         Environment: OpenJDK Runtime Environment AdoptOpenJDK-11.0.11+9 
> (build 11.0.11+9)
> OpenJDK 64-Bit Server VM AdoptOpenJDK-11.0.11+9 (build 11.0.11+9, mixed mode)
> WIN10 (tests) / REL 8 (web application)
> IntelliJ 2021.2 
>            Reporter: mgroovy
>            Priority: Major
>         Attachments: groovy_3_0_9_gc.png, groovy_3_0_9_loop2.png, 
> groovy_3_0_9_loop4.png, groovy_3_0_9_mem.png, groovy_4_0_0_b1_loop2.png, 
> groovy_4_0_0_b1_loop4.png, groovy_4_0_0_b1_loop4_gc.png, 
> groovy_4_0_0_b1_loop4_mem.png, 
> groovysql_performance_groovy4_2_xx_yy_zzzz.groovy, loops.groovy, 
> profile3.txt, profile4-loops.txt, profile4.txt, profile4d.txt
>
>
> Groovy 4.0.0-beta-1 runtime performance in our framework is on average 2 to 3 
> times slower compared to using Groovy 3.0.9 (regular i.e. non-INDY)
> * Our complete framework and application code is completely written in 
> Groovy, spread over multiple IntelliJ modules
> ** mixed @CompileDynamic/@TypeChecked and @CompileStatic
> ** No Java classes left in project, i.e. no cross compilation occurs
> * We build using IntelliJ 2021.2 Groovy build process, then run / deploy the 
> compiled class files
> ** We do _not_ use a Groovy based DSL, nor do we execute Groovy scripts 
> during execution
> * Performance degradation when using Groovy 4.0.0-beta-1 instead of Groovy 
> 3.0.9 (non-INDY):
> ** The performance of the largest of our web applications has dropped 3x 
> (startup) / 2x (table refresh) respectively
> *** Stack: Tomcat/Vaadin/Ebean plus framework generated SQL
> ** Our test suite runs about 2.4 times as long as before (120 min when using 
> G4, compared to about 50 min with G3)
> *** JUnit 5 
> *** test suite also contains no scripts / dynamic code execution
> *** Individual test performance varies: A small number of tests runs faster, 
> but the majority is slower, with some extreme cases taking nearly 10x as long 
> to finish
> * Using Groovy 3.0.9 INDY displays nearly identical performance degradation, 
> so it seems that the use of invoke dynamic is somehow at fault



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to