Skip to content

Commit e910285

Browse files
committed
GROOVY-10307: add targeted JMH benchmarks for SwitchPoint invalidation regression
1 parent 8a40250 commit e910285

File tree

3 files changed

+951
-0
lines changed

3 files changed

+951
-0
lines changed
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.groovy.perf.grails
20+
21+
import groovy.lang.ExpandoMetaClass
22+
import groovy.lang.GroovySystem
23+
24+
import org.openjdk.jmh.annotations.*
25+
import org.openjdk.jmh.infra.Blackhole
26+
27+
import java.util.concurrent.TimeUnit
28+
29+
/**
30+
* SwitchPoint invalidation overhead for Grails-like metaclass change patterns.
31+
*
32+
* @see <a href="https://issues.apache.org/jira/browse/GROOVY-10307">GROOVY-10307</a>
33+
*/
34+
@Warmup(iterations = 3, time = 2, timeUnit = TimeUnit.SECONDS)
35+
@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
36+
@Fork(2)
37+
@BenchmarkMode(Mode.AverageTime)
38+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
39+
@State(Scope.Thread)
40+
class CallSiteInvalidationBench {
41+
static final int ITERATIONS = 100_000
42+
43+
static class HotTarget {
44+
int value = 42
45+
int compute() { value * 2 }
46+
String describe() { "v=$value" }
47+
}
48+
49+
static class HotTargetB {
50+
int count = 10
51+
int getCount() { count }
52+
}
53+
54+
static class HotTargetC {
55+
List items = [1, 2, 3]
56+
int itemCount() { items.size() }
57+
}
58+
59+
static class ColdType {
60+
String label = "cold"
61+
}
62+
63+
HotTarget hotTarget
64+
HotTargetB hotTargetB
65+
HotTargetC hotTargetC
66+
List<Integer> sampleList
67+
68+
@Setup(Level.Iteration)
69+
void setup() {
70+
GroovySystem.metaClassRegistry.removeMetaClass(HotTarget)
71+
GroovySystem.metaClassRegistry.removeMetaClass(HotTargetB)
72+
GroovySystem.metaClassRegistry.removeMetaClass(HotTargetC)
73+
GroovySystem.metaClassRegistry.removeMetaClass(ColdType)
74+
hotTarget = new HotTarget()
75+
hotTargetB = new HotTargetB()
76+
hotTargetC = new HotTargetC()
77+
sampleList = [1, 2, 3, 4, 5]
78+
}
79+
80+
/** Single method call in tight loop, no invalidation. */
81+
@Benchmark
82+
void baselineHotLoop(Blackhole bh) {
83+
int sum = 0
84+
for (int i = 0; i < ITERATIONS; i++) {
85+
sum += hotTarget.compute()
86+
}
87+
bh.consume(sum)
88+
}
89+
90+
/** list.size() in tight loop, no invalidation. */
91+
@Benchmark
92+
void baselineListSize(Blackhole bh) {
93+
int sum = 0
94+
for (int i = 0; i < ITERATIONS; i++) {
95+
sum += sampleList.size()
96+
}
97+
bh.consume(sum)
98+
}
99+
100+
/** Cross-type invalidation every 1000 calls. */
101+
@Benchmark
102+
void crossTypeInvalidationEvery1000(Blackhole bh) {
103+
int sum = 0
104+
for (int i = 0; i < ITERATIONS; i++) {
105+
sum += hotTarget.compute()
106+
if (i % 1000 == 0) {
107+
ColdType.metaClass."dynamic${i % 5}" = { -> i }
108+
}
109+
}
110+
bh.consume(sum)
111+
}
112+
113+
/** Cross-type invalidation every 100 calls. */
114+
@Benchmark
115+
void crossTypeInvalidationEvery100(Blackhole bh) {
116+
int sum = 0
117+
for (int i = 0; i < ITERATIONS; i++) {
118+
sum += hotTarget.compute()
119+
if (i % 100 == 0) {
120+
ColdType.metaClass."dynamic${i % 5}" = { -> i }
121+
}
122+
}
123+
bh.consume(sum)
124+
}
125+
126+
/** Cross-type invalidation every 10000 calls. */
127+
@Benchmark
128+
void crossTypeInvalidationEvery10000(Blackhole bh) {
129+
int sum = 0
130+
for (int i = 0; i < ITERATIONS; i++) {
131+
sum += hotTarget.compute()
132+
if (i % 10000 == 0) {
133+
ColdType.metaClass."dynamic${i % 5}" = { -> i }
134+
}
135+
}
136+
bh.consume(sum)
137+
}
138+
139+
/** list.size() with cross-type invalidation every 1000 calls. */
140+
@Benchmark
141+
void listSizeWithCrossTypeInvalidation(Blackhole bh) {
142+
int sum = 0
143+
for (int i = 0; i < ITERATIONS; i++) {
144+
sum += sampleList.size()
145+
if (i % 1000 == 0) {
146+
ColdType.metaClass."dynamic${i % 5}" = { -> i }
147+
}
148+
}
149+
bh.consume(sum)
150+
}
151+
152+
/** Same-type invalidation every 1000 calls. */
153+
@Benchmark
154+
void sameTypeInvalidationEvery1000(Blackhole bh) {
155+
int sum = 0
156+
for (int i = 0; i < ITERATIONS; i++) {
157+
sum += hotTarget.compute()
158+
if (i % 1000 == 0) {
159+
HotTarget.metaClass."dynamic${i % 5}" = { -> i }
160+
}
161+
}
162+
bh.consume(sum)
163+
}
164+
165+
/** Five method calls across three types, no invalidation. */
166+
@Benchmark
167+
void baselineMultipleCallSites(Blackhole bh) {
168+
int sum = 0
169+
for (int i = 0; i < ITERATIONS; i++) {
170+
sum += hotTarget.compute()
171+
sum += hotTarget.describe().length()
172+
sum += hotTargetB.getCount()
173+
sum += hotTargetC.itemCount()
174+
sum += sampleList.size()
175+
}
176+
bh.consume(sum)
177+
}
178+
179+
/** Five call sites with cross-type invalidation every 1000 calls. */
180+
@Benchmark
181+
void multipleCallSitesWithInvalidation(Blackhole bh) {
182+
int sum = 0
183+
for (int i = 0; i < ITERATIONS; i++) {
184+
sum += hotTarget.compute()
185+
sum += hotTarget.describe().length()
186+
sum += hotTargetB.getCount()
187+
sum += hotTargetC.itemCount()
188+
sum += sampleList.size()
189+
if (i % 1000 == 0) {
190+
ColdType.metaClass."dynamic${i % 5}" = { -> i }
191+
}
192+
}
193+
bh.consume(sum)
194+
}
195+
196+
/** 100 metaclass changes then steady-state method calls. */
197+
@Benchmark
198+
void burstThenSteadyState(Blackhole bh) {
199+
// Phase 1: Burst of metaclass changes (framework startup)
200+
for (int i = 0; i < 100; i++) {
201+
ColdType.metaClass."startup${i % 20}" = { -> i }
202+
}
203+
204+
// Phase 2: Steady-state method calls (request handling)
205+
int sum = 0
206+
for (int i = 0; i < ITERATIONS; i++) {
207+
sum += hotTarget.compute()
208+
sum += hotTargetB.getCount()
209+
sum += sampleList.size()
210+
}
211+
bh.consume(sum)
212+
}
213+
214+
/** Steady-state method calls with no preceding burst. */
215+
@Benchmark
216+
void baselineSteadyStateNoBurst(Blackhole bh) {
217+
int sum = 0
218+
for (int i = 0; i < ITERATIONS; i++) {
219+
sum += hotTarget.compute()
220+
sum += hotTargetB.getCount()
221+
sum += sampleList.size()
222+
}
223+
bh.consume(sum)
224+
}
225+
}

0 commit comments

Comments
 (0)