1 /*
2 * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23 package org.openjdk.bench.vm.compiler;
24
25 import java.lang.reflect.Constructor;
26 import java.lang.reflect.Method;
27 import java.util.*;
28 import java.util.concurrent.ThreadLocalRandom;
29
30 import java.util.concurrent.TimeUnit;
31 import java.util.stream.Collectors;
32 import java.util.stream.IntStream;
33
34 import org.openjdk.jmh.annotations.Benchmark;
35 import org.openjdk.jmh.annotations.BenchmarkMode;
36 import org.openjdk.jmh.annotations.CompilerControl;
37 import org.openjdk.jmh.annotations.Fork;
38 import org.openjdk.jmh.annotations.Level;
39 import org.openjdk.jmh.annotations.Measurement;
40 import org.openjdk.jmh.annotations.Mode;
41 import org.openjdk.jmh.annotations.OutputTimeUnit;
42 import org.openjdk.jmh.annotations.Param;
43 import org.openjdk.jmh.annotations.Scope;
44 import org.openjdk.jmh.annotations.Setup;
45 import org.openjdk.jmh.annotations.State;
46 import org.openjdk.jmh.annotations.Threads;
47 import org.openjdk.jmh.annotations.Warmup;
48 import org.openjdk.jmh.infra.Blackhole;
49
50 import org.openjdk.bench.util.InMemoryJavaCompiler;
51
52 @State(Scope.Benchmark)
53 @Warmup(iterations = 20, time = 2)
54 @Measurement(iterations = 15, time = 2)
55 @BenchmarkMode(Mode.AverageTime)
56 @OutputTimeUnit(TimeUnit.MICROSECONDS)
57 @Threads(1)
58 @Fork(value = 2)
59 public class CodeCacheStress {
60
61 // The number of distinct classes generated from the source string below
62 // All the classes are "warmed up" by invoking their methods to get compiled by the jit
63 @Param({"100"})
64 public int numberOfClasses;
65
66 // The range of these classes to use in the measured phase after the warm up
67 @Param({"100"})
68 public int rangeOfClasses;
69
70 // How deep is the recursion when calling into the generated classes
71 @Param({"20"})
72 public int recurse;
73
74 // How many instances of each generated class to create and call in the measurement phase
75 @Param({"100"})
76 public int instanceCount;
77
78 byte[][] compiledClasses;
79 Class[] loadedClasses;
80 String[] classNames;
81
82 int index = 0;
83 Map<Object, Method[]> classToMethodsMap = new HashMap<>();
84 ArrayList<Map<String, Integer>> argumentMaps = new ArrayList<>();
85 Map<Class, Object[]> instancesOfClassMap = new HashMap<>();
86
87 static final String k = "key";
88 static final Integer v = 1000;
89
90 static final String methodNames[] = {
91 "get"
92 };
93
94 static String B(int count) {
95 return "import java.util.*; "
96 + " "
97 + "public class B" + count + " {"
98 + " "
99 + " static int staticA = 0;"
100 + " static int staticB = 0;"
101 + " static int staticC = 0;"
102 + " static int staticD = 0;"
103 + " "
104 + " static synchronized void setA(int a) {"
105 + " staticB = a;"
106 + " }"
107 + " "
108 + " static synchronized void setB(int b) {"
109 + " staticB = b;"
110 + " }"
111 + " "
112 + " static synchronized void setC(int c) {"
113 + " staticC = c;"
114 + " }"
115 + " "
116 + " static synchronized void setD(int d) {"
117 + " staticD = d;"
118 + " }"
119 + " "
120 + " int instA = 0;"
121 + " "
122 + " int padAA = 0;"
123 + " int padAB = 0;"
124 + " int padAC = 0;"
125 + " int padAD = 0;"
126 + " int padAE = 0;"
127 + " int padAF = 0;"
128 + " int padAG = 0;"
129 + " int padAH = 0;"
130 + " int padAI = 0;"
131 + " int padAJ = 0;"
132 + " int padAK = 0;"
133 + " int padAL = 0;"
134 + " int padAM = 0;"
135 + " int padAN = 0;"
136 + " int padAO = 0;"
137 + " int padAP = 0;"
138 + " int padAQ = 0;"
139 + " int padAR = 0;"
140 + " int padAS = 0;"
141 + " int padAT = 0;"
142 + " "
143 + " int instB = 0;"
144 + " "
145 + " int padBA = 0;"
146 + " int padBB = 0;"
147 + " int padBC = 0;"
148 + " int padBD = 0;"
149 + " int padBE = 0;"
150 + " int padBF = 0;"
151 + " int padBG = 0;"
152 + " int padBH = 0;"
153 + " int padBI = 0;"
154 + " int padBJ = 0;"
155 + " int padBK = 0;"
156 + " int padBL = 0;"
157 + " int padBM = 0;"
158 + " int padBN = 0;"
159 + " int padBO = 0;"
160 + " int padBP = 0;"
161 + " int padBQ = 0;"
162 + " int padBR = 0;"
163 + " int padBS = 0;"
164 + " int padBT = 0;"
165 + " "
166 + " int instC = 0;"
167 + " "
168 + " int padCA = 0;"
169 + " int padCB = 0;"
170 + " int padCC = 0;"
171 + " int padCD = 0;"
172 + " int padCE = 0;"
173 + " int padCF = 0;"
174 + " int padCG = 0;"
175 + " int padCH = 0;"
176 + " int padCI = 0;"
177 + " int padCJ = 0;"
178 + " int padCK = 0;"
179 + " int padCL = 0;"
180 + " int padCM = 0;"
181 + " int padCN = 0;"
182 + " int padCO = 0;"
183 + " int padCP = 0;"
184 + " int padCQ = 0;"
185 + " int padCR = 0;"
186 + " int padCS = 0;"
187 + " int padCT = 0;"
188 + " "
189 + " int instD = 0;"
190 + " "
191 + " "
192 + " public Integer get(Map m, String k, Integer depth) {"
193 + " if (depth > 0) {"
194 + " instA += ((depth % 2) + staticA);"
195 + " return (Integer) m.get(k) + get2(m, k, --depth);"
196 + " } else {"
197 + " setA(depth);"
198 + " return (Integer) m.get(k)+ 10;"
199 + " }"
200 + " }"
201 + " "
202 + " public Integer get2(Map m, String k, Integer depth) {"
203 + " if (depth > 0) {"
204 + " instB += ((depth % 2) + staticB);"
205 + " return (Integer) m.get(k) + get3(m, k, --depth);"
206 + " } else {"
207 + " setB(depth);"
208 + " return (Integer) m.get(k)+ 20;"
209 + " }"
210 + " }"
211 + " "
212 + " public Integer get3(Map m, String k, Integer depth) {"
213 + " if (depth > 0) {"
214 + " instC += ((depth % 2) + staticC);"
215 + " return (Integer) m.get(k) + get4(m, k, --depth);"
216 + " } else {"
217 + " setC(depth);"
218 + " return (Integer) m.get(k)+ 30;"
219 + " }"
220 + " }"
221 + " "
222 + " "
223 + " public Integer get4(Map m, String k, Integer depth) {"
224 + " if (depth > 0) {"
225 + " instD += ((depth % 2) + staticD);"
226 + " return (Integer) m.get(k) + get5(m, k, --depth);"
227 + " } else {"
228 + " setD(depth);"
229 + " return (Integer) m.get(k)+ 40;"
230 + " }"
231 + " }"
232 + " "
233 + " "
234 + " public Integer get5(Map m, String k, Integer depth) {"
235 + " if (depth > 0) {"
236 + " return (Integer) m.get(k) + get6(m, k, --depth);"
237 + " } else {"
238 + " return (Integer) m.get(k)+ instA;"
239 + " }"
240 + " }"
241 + " "
242 + " public Integer get6(Map m, String k, Integer depth) {"
243 + " if (depth > 0) {"
244 + " return (Integer) m.get(k) + get7(m, k, --depth);"
245 + " } else {"
246 + " return (Integer) m.get(k)+ instB;"
247 + " }"
248 + " }"
249 + " "
250 + " public Integer get7(Map m, String k, Integer depth) {"
251 + " if (depth > 0) {"
252 + " return (Integer) m.get(k) + get8(m, k, --depth);"
253 + " } else {"
254 + " return (Integer) m.get(k)+ instC;"
255 + " }"
256 + " }"
257 + " "
258 + " public Integer get8(Map m, String k, Integer depth) {"
259 + " if (depth > 0) {"
260 + " return (Integer) m.get(k) + get9(m, k, --depth);"
261 + " } else {"
262 + " return (Integer) m.get(k)+ instD;"
263 + " }"
264 + " }"
265 + " "
266 + " public Integer get9(Map m, String k, Integer depth) {"
267 + " if (depth > 0) {"
268 + " return (Integer) m.get(k) + get10(m, k, --depth);"
269 + " } else {"
270 + " return (Integer) m.get(k)+ instA;"
271 + " }"
272 + " }"
273 + " "
274 + " public Integer get10(Map m, String k, Integer depth) {"
275 + " if (depth > 0) {"
276 + " return (Integer) m.get(k) + get11(m, k, --depth);"
277 + " } else {"
278 + " return (Integer) m.get(k)+ instB;"
279 + " }"
280 + " }"
281 + " "
282 + " public Integer get11(Map m, String k, Integer depth) {"
283 + " if (depth > 0) {"
284 + " return (Integer) m.get(k) + get12(m, k, --depth);"
285 + " } else {"
286 + " return (Integer) m.get(k)+ instC;"
287 + " }"
288 + " }"
289 + " "
290 + " public Integer get12(Map m, String k, Integer depth) {"
291 + " if (depth > 0) {"
292 + " return (Integer) m.get(k) + get(m, k, --depth);"
293 + " } else {"
294 + " return (Integer) m.get(k)+ instD;"
295 + " }"
296 + " }"
297 + "}";
298 }
299
300
301 class BenchLoader extends ClassLoader {
302
303 BenchLoader() {
304 super();
305 }
306
307 BenchLoader(ClassLoader parent) {
308 super(parent);
309 }
310
311 @Override
312 protected Class<?> findClass(String name) throws ClassNotFoundException {
313 if (name.equals(classNames[index])) {
314 assert compiledClasses[index] != null;
315 return defineClass(name, compiledClasses[index],
316 0,
317 (compiledClasses[index]).length);
318 } else {
319 return super.findClass(name);
320 }
321 }
322 }
323
324 @SuppressWarnings("initialization")
325 CodeCacheStress.BenchLoader loader1 = new CodeCacheStress.BenchLoader();
326
327 @Setup(Level.Trial)
328 public void setupClasses() throws Exception {
329 Object[] receivers1;
330
331 compiledClasses = new byte[numberOfClasses][];
332 loadedClasses = new Class[numberOfClasses];
333 classNames = new String[numberOfClasses];
334
335 argumentMaps.add(new HashMap<String, Integer>());
336 argumentMaps.add(new LinkedHashMap<String, Integer>());
337 argumentMaps.add(new WeakHashMap<String, Integer>());
338
339 argumentMaps.get(0).put(k, v);
340 argumentMaps.get(1).put(k, v);
341 argumentMaps.get(2).put(k, v);
342
343 for (int i = 0; i < numberOfClasses; i++) {
344 classNames[i] = "B" + i;
345 compiledClasses[i] = InMemoryJavaCompiler.compile(classNames[i], B(i));
346 }
347
348 for (index = 0; index < compiledClasses.length; index++) {
349 Class<?> c = loader1.findClass(classNames[index]);
350 loadedClasses[index] = c;
351
352 Constructor<?>[] ca = c.getConstructors();
353 assert ca.length == 1;
354
355 receivers1 = new Object[instanceCount];
356 for (int j = 0; j < instanceCount; j++) {
357 receivers1[j] = ca[0].newInstance();
358 }
359 instancesOfClassMap.put(c, receivers1);
360
361 Method[] methods = new Method[methodNames.length];
362 IntStream.range(0, methodNames.length).forEach(m -> {
363 try {
364 methods[m] = c.getMethod(methodNames[m], java.util.Map.class, String.class, Integer.class);
365 } catch (Exception e) {
366 System.out.println("Exception = " + e);
367 e.printStackTrace();
368 System.exit(-1);
369 }
370 });
371
372 classToMethodsMap.put((receivers1[0]).getClass(), methods);
373
374 // Warmup the methods to get compiled
375 IntStream.range(0, methodNames.length).parallel().forEach(m -> {
376 IntStream.range(0, 12000).forEach(x -> {
377 try {
378 Object r = ((Object[]) instancesOfClassMap.get(c))[0];
379 Method[] mi = classToMethodsMap.get(r.getClass());
380 mi[m].invoke(r, argumentMaps.get(0), k, 5);
381 } catch (Exception e) {
382 System.out.println("Exception = " + e);
383 e.printStackTrace();
384 System.exit(-1);
385 }
386 });
387
388 });
389 }
390
391 System.gc();
392 }
393
394 Class chooseClass() {
395 ThreadLocalRandom tlr = ThreadLocalRandom.current();
396 int whichClass = tlr.nextInt(rangeOfClasses);
397 return loadedClasses[whichClass];
398 }
399
400 Object chooseInstance(Class c) {
401 ThreadLocalRandom tlr = ThreadLocalRandom.current();
402 int whichInst = tlr.nextInt(instanceCount);
403 return ((Object[]) instancesOfClassMap.get(c))[whichInst];
404 }
405
406 Method chooseMethod(Class c) {
407 ThreadLocalRandom tlr = ThreadLocalRandom.current();
408 int whichM = tlr.nextInt(methodNames.length);
409 return classToMethodsMap.get(c)[whichM];
410 }
411
412 Map chooseMap() {
413 ThreadLocalRandom tlr = ThreadLocalRandom.current();
414 int whichMap = tlr.nextInt(argumentMaps.size());
415 return argumentMaps.get(whichMap);
416 }
417
418 Integer callTheMethod(Method m, Object r, String k, Map map) throws Exception {
419 return (Integer) m.invoke(r, map, k, recurse);
420 }
421
422 @Benchmark
423 public Integer work() throws Exception {
424 int sum = 0;
425
426 // Call a method of a random instance of a random class up to the specified range
427 for (int index = 0; index < compiledClasses.length; index++) {
428 try {
429 Class c = chooseClass();
430 Object r = chooseInstance(c);
431 Method m = chooseMethod(c);
432 assert m != null;
433 Map map = chooseMap();
434 Integer result = callTheMethod(m, r, k, map);
435 assert result != null && result >= v;
436 sum += result;
437 } catch (Exception e) {
438 System.out.println("Exception = " + e);
439 e.printStackTrace();
440 }
441 }
442 return sum;
443 }
444
445 }