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 }