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