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
24 package org.openjdk.bench.loom;
25
26 import org.openjdk.jmh.annotations.Benchmark;
27 import org.openjdk.jmh.annotations.BenchmarkMode;
28 import org.openjdk.jmh.annotations.Fork;
29 import org.openjdk.jmh.annotations.Level;
30 import org.openjdk.jmh.annotations.Measurement;
31 import org.openjdk.jmh.annotations.Mode;
32 import org.openjdk.jmh.annotations.OutputTimeUnit;
33 import org.openjdk.jmh.annotations.Param;
34 import org.openjdk.jmh.annotations.Scope;
35 import org.openjdk.jmh.annotations.Setup;
36 import org.openjdk.jmh.annotations.TearDown;
37 import org.openjdk.jmh.annotations.State;
38 import org.openjdk.jmh.annotations.Warmup;
39
40 import java.util.concurrent.atomic.AtomicInteger;
41 import java.util.concurrent.locks.ReentrantLock;
42 import java.util.concurrent.*;
43
44 import java.lang.reflect.Constructor;
45 import java.lang.reflect.InvocationTargetException;
46
47 //@Fork(2)
48 @Fork(1)
49 @Warmup(iterations = 3, time = 5)
50 @Measurement(iterations = 3, time = 5)
51 @BenchmarkMode(Mode.AverageTime)
52 @OutputTimeUnit(TimeUnit.NANOSECONDS)
53 @State(Scope.Thread)
54 @SuppressWarnings("preview")
55 public class Monitors2 {
56 static Object[] globalLockArray;
57 static ReentrantLock[] globalReentrantLockArray;
58 static AtomicInteger workerCount = new AtomicInteger(0);
59 static AtomicInteger dummyCounter = new AtomicInteger(0);
60 static int workIterations;
61
62 static int VT_COUNT;
63 static int CARRIER_COUNT = 8;
64 static ExecutorService scheduler = Executors.newFixedThreadPool(CARRIER_COUNT);
65
66 //@Param({"8", "32", "256", "4096", "131072"})
67 @Param({"8", "32", "256", "4096"})
68 static int VT_MULTIPLIER;
69
70 @Param({"1", "5", "12", "32"})
71 static int MONITORS_CNT;
72
73 @Param({"0", "50000", "100000", "200000"})
74 static int WORKLOAD;
75
76 @Param({"10"})
77 static int STACK_DEPTH;
78
79 void recursive4_1(int depth, int lockNumber) {
80 if (depth > 0) {
81 recursive4_1(depth - 1, lockNumber);
82 } else {
83 if (Math.random() < 0.5) {
84 Thread.yield();
85 }
86 recursive4_2(lockNumber);
87 }
88 }
89
90 void recursive4_2(int lockNumber) {
91 if (lockNumber + 2 <= MONITORS_CNT - 1) {
92 lockNumber += 2;
93 synchronized(globalLockArray[lockNumber]) {
94 Thread.yield();
95 recursive4_2(lockNumber);
96 }
97 }
98 }
99
100 final Runnable FOO4 = () -> {
101 int iterations = 10;
102 while (iterations-- > 0) {
103 int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITORS_CNT - 1);
104 synchronized(globalLockArray[lockNumber]) {
105 recursive4_1(lockNumber, lockNumber);
106 }
107 }
108 workerCount.getAndIncrement();
109 };
110
111 /**
112 * Test contention on monitorenter with extra monitors on stack shared by all threads.
113 */
114 //@Benchmark
115 public void testContentionMultipleMonitors(MyState state) throws Exception {
116 workerCount.getAndSet(0);
117
118 Thread batch[] = new Thread[VT_COUNT];
119 for (int i = 0; i < VT_COUNT; i++) {
120 batch[i] = virtualThreadBuilder(scheduler).name("BatchVT-" + i).start(FOO4);
121 }
122
123 for (int i = 0; i < VT_COUNT; i++) {
124 batch[i].join();
125 }
126
127 if (workerCount.get() != VT_COUNT) {
128 throw new RuntimeException("testContentionMultipleMonitors failed. Expected " + VT_COUNT + "but found " + workerCount.get());
129 }
130 }
131
132 static void recursive5_1(int depth, int lockNumber, Object[] myLockArray) {
133 if (depth > 0) {
134 recursive5_1(depth - 1, lockNumber, myLockArray);
135 } else {
136 if (Math.random() < 0.5) {
137 Thread.yield();
138 }
139 recursive5_2(lockNumber, myLockArray);
140 }
141 }
142
143 static void recursive5_2(int lockNumber, Object[] myLockArray) {
144 if (lockNumber + 2 <= MONITORS_CNT - 1) {
145 lockNumber += 2;
146 synchronized (myLockArray[lockNumber]) {
147 if (Math.random() < 0.5) {
148 Thread.yield();
149 }
150 synchronized (globalLockArray[lockNumber]) {
151 Thread.yield();
152 recursive5_2(lockNumber, myLockArray);
153 }
154 }
155 }
156 }
157
158 static final Runnable FOO5 = () -> {
159 Object[] myLockArray = new Object[MONITORS_CNT];
160 for (int i = 0; i < MONITORS_CNT; i++) {
161 myLockArray[i] = new Object();
162 }
163
164 int iterations = 10;
165 while (iterations-- > 0) {
166 int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITORS_CNT - 1);
167 synchronized (myLockArray[lockNumber]) {
168 synchronized (globalLockArray[lockNumber]) {
169 recursive5_1(lockNumber, lockNumber, myLockArray);
170 }
171 }
172 }
173 workerCount.getAndIncrement();
174 };
175
176 /**
177 * Test contention on monitorenter with extra monitors on stack both local only and shared by all threads.
178 */
179 //@Benchmark
180 public void testContentionMultipleMonitors2(MyState state) throws Exception {
181 workerCount.getAndSet(0);
182
183 // Create batch of VT threads.
184 Thread batch[] = new Thread[VT_COUNT];
185 for (int i = 0; i < VT_COUNT; i++) {
186 //Thread.ofVirtual().name("FirstBatchVT-" + i).start(FOO);
187 batch[i] = virtualThreadBuilder(scheduler).name("BatchVT-" + i).start(FOO5);
188 }
189
190 for (int i = 0; i < VT_COUNT; i++) {
191 batch[i].join();
192 }
193
194 if (workerCount.get() != VT_COUNT) {
195 throw new RuntimeException("testContentionMultipleMonitors2 failed. Expected " + VT_COUNT + "but found " + workerCount.get());
196 }
197 }
198
199 static void recursiveSync(int depth) {
200 if (depth > 0) {
201 recursiveSync(depth - 1);
202 } else {
203 int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITORS_CNT);
204 synchronized(globalLockArray[lockNumber]) {
205 //Thread.yield();
206 for (int i = 0; i < WORKLOAD; i++) {
207 dummyCounter.getAndIncrement();
208 }
209 workerCount.getAndIncrement();
210 }
211 }
212 }
213
214 static final Runnable SYNC = () -> {
215 recursiveSync(STACK_DEPTH);
216 };
217
218 static void recursiveReentrant(int depth) {
219 if (depth > 0) {
220 recursiveReentrant(depth - 1);
221 } else {
222 int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITORS_CNT);
223 globalReentrantLockArray[lockNumber].lock();
224 //Thread.yield();
225 for (int i = 0; i < WORKLOAD; i++) {
226 dummyCounter.getAndIncrement();
227 }
228 workerCount.getAndIncrement();
229 globalReentrantLockArray[lockNumber].unlock();
230 }
231 }
232
233 static final Runnable REENTRANTLOCK = () -> {
234 recursiveReentrant(STACK_DEPTH);
235 };
236
237 public void runBenchmark(Runnable r) throws Exception {
238 workerCount.getAndSet(0);
239
240 // Create batch of VT threads.
241 Thread batch[] = new Thread[VT_COUNT];
242 for (int i = 0; i < VT_COUNT; i++) {
243 batch[i] = virtualThreadBuilder(scheduler).name("BatchVT-" + i).start(r);
244 }
245
246 for (int i = 0; i < VT_COUNT; i++) {
247 batch[i].join();
248 }
249
250 if (workerCount.get() != VT_COUNT) {
251 throw new RuntimeException("testContentionMultipleMonitors2 failed. Expected " + VT_COUNT + "but found " + workerCount.get());
252 }
253 }
254
255 @Benchmark
256 public void testContentionReentrantLock(MyState state) throws Exception {
257 runBenchmark(REENTRANTLOCK);
258 }
259
260 @Benchmark
261 public void testContentionASync(MyState state) throws Exception {
262 runBenchmark(SYNC);
263 }
264
265 //@Benchmark
266 public void testExtraTime(MyState state) throws Exception {
267 dummyCounter.getAndSet(0);
268 // Takes ~120us
269 for (int i = 0; i < 50000; i++) {
270 dummyCounter.getAndIncrement();
271 }
272 if (dummyCounter.get() != 50000) {
273 throw new RuntimeException("testContentionMultipleMonitors2 failed. Expected " + 50000 + "but found " + dummyCounter.get());
274 }
275 }
276
277 @State(Scope.Thread)
278 public static class MyState {
279
280 @Setup(Level.Trial)
281 public void doSetup() {
282 // Setup up monitors/locks
283 globalLockArray = new Object[MONITORS_CNT];
284 globalReentrantLockArray = new ReentrantLock[MONITORS_CNT];
285 for (int i = 0; i < MONITORS_CNT; i++) {
286 globalLockArray[i] = new Object();
287 globalReentrantLockArray[i] = new ReentrantLock();
288 }
289 // Setup VirtualThread count
290 VT_COUNT = CARRIER_COUNT * VT_MULTIPLIER;
291
292 System.out.println("Running test with MONITORS_CNT = " + MONITORS_CNT + " VT_COUNT = " + VT_COUNT + " and WORKLOAD = " + WORKLOAD);
293 //System.out.println("Running test with VT_COUNT = " + VT_COUNT);
294 }
295
296 @TearDown(Level.Trial)
297 public void doTearDown() {
298 scheduler.shutdown();
299 System.out.println("Do TearDown");
300 }
301 }
302
303 private static Thread.Builder.OfVirtual virtualThreadBuilder(Executor scheduler) {
304 Thread.Builder.OfVirtual builder = Thread.ofVirtual();
305 try {
306 Class<?> clazz = Class.forName("java.lang.ThreadBuilders$VirtualThreadBuilder");
307 Constructor<?> ctor = clazz.getDeclaredConstructor(Executor.class);
308 ctor.setAccessible(true);
309 return (Thread.Builder.OfVirtual) ctor.newInstance(scheduler);
310 } catch (InvocationTargetException e) {
311 Throwable cause = e.getCause();
312 if (cause instanceof RuntimeException re) {
313 throw re;
314 }
315 throw new RuntimeException(e);
316 } catch (Exception e) {
317 throw new RuntimeException(e);
318 }
319 }
320 }