1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 import java.util.concurrent.atomic.AtomicInteger; 17 18 public class Main { 19 20 private final boolean PRINT_TIMES = false; // False for use as run test. 21 22 // Number of increments done by each thread. Must be multiple of largest hold time below, 23 // times any possible thread count. We reduce this below if PRINT_TIMES is not set, and 24 // even more in debuggable mode. 25 private final int UNSCALED_TOTAL_ITERS = 16_000_000; 26 private final int UNSCALED_MAX_HOLD_TIME = 2_000_000; 27 private int totalIters; 28 private int maxHoldTime; 29 30 private int counter; 31 32 private AtomicInteger atomicCounter = new AtomicInteger(); 33 34 private Object lock; 35 36 private int currentThreadCount = 0; 37 38 private static boolean debuggable = false; // Running in a debuggable ART environment. 39 40 // A function such that if we repeatedly apply it to -1, the value oscillates 41 // between -1 and 3. Thus the average value is 1. 42 // This is designed to make it hard for the compiler to predict the values in 43 // the sequence. nextInt(int x)44 private int nextInt(int x) { 45 if (x < 0) { 46 return x * x + 2; 47 } else { 48 return x - 4; 49 } 50 } 51 52 // Increment counter by n, holding lock for time roughly propertional to n. 53 // N must be even. holdFor(Object lock, int n)54 private void holdFor(Object lock, int n) { 55 synchronized(lock) { 56 int y = -1; 57 for (int i = 0; i < n; ++i) { 58 counter += y; 59 y = nextInt(y); 60 } 61 } 62 } 63 64 // Increment local by an even number n in a way that takes time roughly proportional 65 // to n. spinFor(int n)66 private void spinFor(int n) { 67 int y = -1; 68 int local_counter = 0; 69 for (int i = 0; i < n; ++i) { 70 local_counter += y; 71 y = nextInt(y); 72 } 73 if (local_counter != n) { 74 throw new Error(); 75 } 76 } 77 78 private class RepeatedLockHolder implements Runnable { RepeatedLockHolder(boolean shared, int n )79 RepeatedLockHolder(boolean shared, int n /* even */) { 80 sharedLock = shared; 81 holdTime = n; 82 } 83 @Override run()84 public void run() { 85 Object myLock = sharedLock ? lock : new Object(); 86 int nIters = totalIters / currentThreadCount / holdTime; 87 for (int i = 0; i < nIters; ++i) { 88 holdFor(myLock, holdTime); 89 } 90 } 91 private boolean sharedLock; 92 private int holdTime; 93 } 94 95 private class RepeatedIntermittentLockHolder implements Runnable { RepeatedIntermittentLockHolder(boolean shared, int n )96 RepeatedIntermittentLockHolder(boolean shared, int n /* even */) { 97 sharedLock = shared; 98 holdTime = n; 99 } 100 @Override run()101 public void run() { 102 Object myLock = sharedLock ? lock : new Object(); 103 int iter_divisor = 10 * currentThreadCount * holdTime; 104 int nIters = totalIters / iter_divisor; 105 if (totalIters % iter_divisor != 0 || holdTime % 2 == 1) { 106 System.err.println("Misconfigured: totalIters = " + totalIters 107 + " iter_divisor = " + iter_divisor); 108 } 109 for (int i = 0; i < nIters; ++i) { 110 spinFor(9 * holdTime); 111 holdFor(myLock, holdTime); 112 } 113 } 114 private boolean sharedLock; 115 private int holdTime; 116 } 117 118 private class SleepyLockHolder implements Runnable { SleepyLockHolder(boolean shared)119 SleepyLockHolder(boolean shared) { 120 sharedLock = shared; 121 } 122 @Override run()123 public void run() { 124 Object myLock = sharedLock ? lock : new Object(); 125 int nIters = totalIters / currentThreadCount / 10_000; 126 for (int i = 0; i < nIters; ++i) { 127 synchronized(myLock) { 128 try { 129 Thread.sleep(2); 130 } catch(InterruptedException e) { 131 throw new AssertionError("Unexpected interrupt"); 132 } 133 counter += 10_000; 134 } 135 } 136 } 137 private boolean sharedLock; 138 } 139 140 // Increment atomicCounter n times, on average by 1 each time. 141 private class RepeatedIncrementer implements Runnable { 142 @Override run()143 public void run() { 144 int y = -1; 145 int nIters = totalIters / currentThreadCount; 146 for (int i = 0; i < nIters; ++i) { 147 atomicCounter.addAndGet(y); 148 y = nextInt(y); 149 } 150 } 151 } 152 153 // Run n threads doing work. Return the elapsed time this took, in milliseconds. runMultiple(int n, Runnable work)154 private long runMultiple(int n, Runnable work) { 155 Thread[] threads = new Thread[n]; 156 // Replace lock, so that we start with a clean, uninflated lock each time. 157 lock = new Object(); 158 for (int i = 0; i < n; ++i) { 159 threads[i] = new Thread(work); 160 } 161 long startTime = System.currentTimeMillis(); 162 for (int i = 0; i < n; ++i) { 163 threads[i].start(); 164 } 165 for (int i = 0; i < n; ++i) { 166 try { 167 threads[i].join(); 168 } catch(InterruptedException e) { 169 throw new AssertionError("Unexpected interrupt"); 170 } 171 } 172 return System.currentTimeMillis() - startTime; 173 } 174 175 // Run on different numbers of threads. runAll(Runnable work, Runnable init, Runnable checker)176 private void runAll(Runnable work, Runnable init, Runnable checker) { 177 for (int i = 1; i <= 8; i *= 2) { 178 currentThreadCount = i; 179 init.run(); 180 long time = runMultiple(i, work); 181 if (PRINT_TIMES) { 182 System.out.print(time + (i == 8 ? "\n" : "\t")); 183 } 184 checker.run(); 185 } 186 } 187 188 private class CheckAtomicCounter implements Runnable { 189 @Override run()190 public void run() { 191 if (atomicCounter.get() != totalIters) { 192 throw new AssertionError("Failed atomicCounter postcondition check for " 193 + currentThreadCount + " threads"); 194 } 195 } 196 } 197 198 private class CheckCounter implements Runnable { 199 private final int expected; CheckCounter(int e)200 public CheckCounter(int e) { 201 expected = e; 202 } 203 @Override run()204 public void run() { 205 if (counter != expected) { 206 throw new AssertionError("Failed counter postcondition check for " 207 + currentThreadCount + " threads, expected " + expected + " got " + counter); 208 } 209 } 210 } 211 run()212 private void run() { 213 int scale = PRINT_TIMES ? 1 : (debuggable ? 20 : 10); 214 totalIters = UNSCALED_TOTAL_ITERS / scale; 215 maxHoldTime = UNSCALED_MAX_HOLD_TIME / scale; 216 if (PRINT_TIMES) { 217 System.out.println("All times in milliseconds for 1, 2, 4 and 8 threads"); 218 } 219 System.out.println("Atomic increments"); 220 runAll(new RepeatedIncrementer(), () -> { atomicCounter.set(0); }, new CheckAtomicCounter()); 221 for (int i = 2; i <= UNSCALED_MAX_HOLD_TIME / 100; i *= 10) { 222 // i * 8 (max thread count) divides totalIters 223 System.out.println("Hold time " + i + ", shared lock"); 224 runAll(new RepeatedLockHolder(true, i), () -> { counter = 0; }, 225 new CheckCounter(totalIters)); 226 } 227 for (int i = 2; i <= UNSCALED_MAX_HOLD_TIME / 1000; i *= 10) { 228 // i * 8 (max thread count) divides totalIters 229 System.out.println("Hold time " + i + ", pause time " + (9 * i) + ", shared lock"); 230 runAll(new RepeatedIntermittentLockHolder(true, i), () -> { counter = 0; }, 231 new CheckCounter(totalIters / 10)); 232 } 233 if (PRINT_TIMES) { 234 for (int i = 2; i <= maxHoldTime; i *= 1000) { 235 // i divides totalIters 236 System.out.println("Hold time " + i + ", private lock"); 237 // Since there is no mutual exclusion final counter value is unpredictable. 238 runAll(new RepeatedLockHolder(false, i), () -> { counter = 0; }, () -> {}); 239 } 240 } 241 System.out.println("Hold for 2 msecs while sleeping, shared lock"); 242 runAll(new SleepyLockHolder(true), () -> { counter = 0; }, new CheckCounter(totalIters)); 243 System.out.println("Hold for 2 msecs while sleeping, private lock"); 244 runAll(new SleepyLockHolder(false), () -> { counter = 0; }, () -> {}); 245 } 246 main(String[] args)247 public static void main(String[] args) { 248 if (System.getProperty("java.vm.name").equalsIgnoreCase("Dalvik")) { 249 try { 250 System.loadLibrary(args[0]); 251 debuggable = isDebuggable(); 252 } catch (Throwable t) { 253 // Conservatively assume we might be debuggable, and pretend we loaded the library. 254 // As of June 2024, we seem to get here with atest. 255 debuggable = true; 256 System.out.println("JNI_OnLoad called"); 257 } 258 } else { 259 System.out.println("JNI_OnLoad called"); // Not really, but match expected output. 260 } 261 System.out.println("Starting"); 262 new Main().run(); 263 } 264 isDebuggable()265 private static native boolean isDebuggable(); 266 } 267