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

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

jamesfredley commented on code in PR #2390:
URL: https://github.com/apache/groovy/pull/2390#discussion_r2878202417


##########
subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/grails/CallSiteInvalidationBench.groovy:
##########
@@ -0,0 +1,225 @@
+/*
+ *  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.grails
+
+import groovy.lang.ExpandoMetaClass

Review Comment:
   Removed in 1af1c45.



##########
subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/grails/MetaclassVariationBench.groovy:
##########
@@ -0,0 +1,259 @@
+/*
+ *  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.grails
+
+import groovy.lang.ExpandoMetaClass
+import groovy.lang.GroovySystem
+
+import org.openjdk.jmh.annotations.*
+import org.openjdk.jmh.infra.Blackhole
+
+import java.util.concurrent.TimeUnit
+
+/**
+ * Per-instance metaclass variation overhead (GORM domain class enhancement 
pattern).
+ *
+ * @see <a 
href="https://issues.apache.org/jira/browse/GROOVY-10307";>GROOVY-10307</a>
+ */
+@Warmup(iterations = 3, time = 2, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Fork(2)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Thread)
+class MetaclassVariationBench {
+    static final int ITERATIONS = 100_000
+    static final int INSTANCE_COUNT = 20
+
+    // Simulates a GORM domain class
+    static class DomainEntity {
+        Long id
+        String name
+        String email
+        boolean active = true
+        int version = 0
+
+        String getFullName() { name ?: 'Unknown' }
+        boolean isActive() { active }
+        int getVersion() { version }
+
+        DomainEntity save() {
+            version++
+            if (id == null) id = System.nanoTime()
+            this
+        }
+
+        Map toMap() {
+            [id: id, name: name, email: email, active: active, version: 
version]
+        }
+    }
+
+    // Additional domain class types
+    static class DomainTypeB {
+        String label = "dept"
+        int count = 5
+        int getCount() { count }
+    }
+
+    static class DomainTypeC {
+        String status = "ACTIVE"
+        BigDecimal budget = 100000.0
+        String getStatus() { status }
+    }
+
+    static class DomainTypeD {
+        int priority = 5
+        String assignee = "unassigned"
+        int getPriority() { priority }
+    }
+
+    // Unrelated type for cross-type invalidation
+    static class ServiceType {
+        String config = "default"
+    }
+
+    List<DomainEntity> sharedMetaclassInstances
+    List<DomainEntity> perInstanceMetaclassInstances
+    DomainTypeB typeB
+    DomainTypeC typeC
+    DomainTypeD typeD
+
+    @Setup(Level.Iteration)
+    void setup() {
+        GroovySystem.metaClassRegistry.removeMetaClass(DomainEntity)
+        GroovySystem.metaClassRegistry.removeMetaClass(DomainTypeB)
+        GroovySystem.metaClassRegistry.removeMetaClass(DomainTypeC)
+        GroovySystem.metaClassRegistry.removeMetaClass(DomainTypeD)
+        GroovySystem.metaClassRegistry.removeMetaClass(ServiceType)
+
+        // Shared default class metaclass
+        sharedMetaclassInstances = (1..INSTANCE_COUNT).collect { i ->
+            new DomainEntity(id: i, name: "User$i", email: "user${i}@test.com")
+        }
+
+        // Per-instance ExpandoMetaClass (GORM trait pattern)
+        perInstanceMetaclassInstances = (1..INSTANCE_COUNT).collect { i ->
+            def entity = new DomainEntity(id: i, name: "Enhanced$i", email: 
"e${i}@test.com")
+            def emc = new ExpandoMetaClass(DomainEntity, false, true)
+            // GORM-injected methods
+            emc.validate = { -> delegate.name != null && delegate.email != 
null }
+            emc.delete = { -> delegate.id = null; delegate }
+            emc.addToDependencies = { item -> delegate }
+            emc.initialize()
+            entity.metaClass = emc
+            entity
+        }
+
+        typeB = new DomainTypeB()
+        typeC = new DomainTypeC()
+        typeD = new DomainTypeD()
+    }
+
+    /** Method calls on instances sharing default class metaclass. */
+    @Benchmark
+    void baselineSharedMetaclass(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            def entity = sharedMetaclassInstances[i % INSTANCE_COUNT]
+            sum += entity.getFullName().length()
+            sum += entity.getVersion()
+        }
+        bh.consume(sum)
+    }
+
+    /** Method calls on instances each with their own ExpandoMetaClass. */
+    @Benchmark
+    void perInstanceMetaclass(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            def entity = perInstanceMetaclassInstances[i % INSTANCE_COUNT]
+            sum += entity.getFullName().length()
+            sum += entity.getVersion()
+        }
+        bh.consume(sum)
+    }
+
+    /** Calling GORM-injected methods on per-instance EMC objects. */
+    @Benchmark
+    void perInstanceInjectedMethodCalls(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            def entity = perInstanceMetaclassInstances[i % INSTANCE_COUNT]
+            boolean valid = entity.validate()
+            sum += valid ? 1 : 0
+        }
+        bh.consume(sum)
+    }
+
+    /** GORM startup: enhance 4 domain types then steady-state calls. */
+    @Benchmark
+    void multiClassStartupThenSteadyState(Blackhole bh) {
+        // Phase 1: Enhance 4 domain class types
+        DomainEntity.metaClass.static.findAllByName = { String n -> [] }
+        DomainEntity.metaClass.static.countByActive = { boolean a -> 0 }
+
+        DomainTypeB.metaClass.static.findAllByLabel = { String l -> [] }
+        DomainTypeB.metaClass.static.countByCount = { int c -> 0 }
+
+        DomainTypeC.metaClass.static.findAllByStatus = { String s -> [] }
+        DomainTypeC.metaClass.static.findByBudgetGreaterThan = { BigDecimal b 
-> null }
+
+        DomainTypeD.metaClass.static.findAllByPriority = { int p -> [] }
+        DomainTypeD.metaClass.static.findByAssignee = { String a -> null }
+
+        // Phase 2: Steady-state calls
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            def entity = sharedMetaclassInstances[i % INSTANCE_COUNT]
+            sum += entity.getFullName().length()
+            sum += typeB.getCount()
+            sum += typeC.getStatus().length()
+            sum += typeD.getPriority()
+        }
+        bh.consume(sum)
+    }
+
+    /** Baseline: same steady-state work without preceding metaclass 
enhancements. */
+    @Benchmark
+    void baselineMultiClassNoStartup(Blackhole bh) {
+        int sum = 0
+        for (int i = 0; i < ITERATIONS; i++) {
+            def entity = sharedMetaclassInstances[i % INSTANCE_COUNT]
+            sum += entity.getFullName().length()
+            sum += typeB.getCount()
+            sum += typeC.getStatus().length()
+            sum += typeD.getPriority()
+        }
+        bh.consume(sum)
+    }
+
+    /** Calling dynamic finders injected via static metaclass. */
+    @Benchmark
+    void dynamicFinderCalls(Blackhole bh) {
+        // Inject dynamic finders
+        DomainEntity.metaClass.static.findByName = { String n ->
+            [new DomainEntity(name: n)]
+        }
+        DomainEntity.metaClass.static.findAllByActive = { boolean a ->

Review Comment:
   The injection inside the benchmark method is intentional - it models the 
per-request cost in Grails where GORM dynamic finders are resolved/injected at 
runtime, not a one-time startup cost. Updated the Javadoc to make this intent 
explicit in 1af1c45.





> 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