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 }