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. Finishes much faster when used as run test. 24 private final int TOTAL_ITERS = PRINT_TIMES? 16_000_000 : 1_600_000; 25 private final int MAX_HOLD_TIME = PRINT_TIMES? 2_000_000 : 200_000; 26 27 private int counter; 28 29 private AtomicInteger atomicCounter = new AtomicInteger(); 30 31 private Object lock; 32 33 private int currentThreadCount = 0; 34 35 // A function such that if we repeatedly apply it to -1, the value oscillates 36 // between -1 and 3. Thus the average value is 1. 37 // This is designed to make it hard for the compiler to predict the values in 38 // the sequence. nextInt(int x)39 private int nextInt(int x) { 40 if (x < 0) { 41 return x * x + 2; 42 } else { 43 return x - 4; 44 } 45 } 46 47 // Increment counter by n, holding lock for time roughly propertional to n. 48 // N must be even. holdFor(Object lock, int n)49 private void holdFor(Object lock, int n) { 50 synchronized(lock) { 51 int y = -1; 52 for (int i = 0; i < n; ++i) { 53 counter += y; 54 y = nextInt(y); 55 } 56 } 57 } 58 59 // Increment local by an even number n in a way that takes time roughly proportional 60 // to n. spinFor(int n)61 private void spinFor(int n) { 62 int y = -1; 63 int local_counter = 0; 64 for (int i = 0; i < n; ++i) { 65 local_counter += y; 66 y = nextInt(y); 67 } 68 if (local_counter != n) { 69 throw new Error(); 70 } 71 } 72 73 private class RepeatedLockHolder implements Runnable { RepeatedLockHolder(boolean shared, int n )74 RepeatedLockHolder(boolean shared, int n /* even */) { 75 sharedLock = shared; 76 holdTime = n; 77 } 78 @Override run()79 public void run() { 80 Object myLock = sharedLock ? lock : new Object(); 81 int nIters = TOTAL_ITERS / currentThreadCount / holdTime; 82 for (int i = 0; i < nIters; ++i) { 83 holdFor(myLock, holdTime); 84 } 85 } 86 private boolean sharedLock; 87 private int holdTime; 88 } 89 90 private class RepeatedIntermittentLockHolder implements Runnable { RepeatedIntermittentLockHolder(boolean shared, int n )91 RepeatedIntermittentLockHolder(boolean shared, int n /* even */) { 92 sharedLock = shared; 93 holdTime = n; 94 } 95 @Override run()96 public void run() { 97 Object myLock = sharedLock ? lock : new Object(); 98 int nIters = TOTAL_ITERS / 10 / currentThreadCount / holdTime; 99 for (int i = 0; i < nIters; ++i) { 100 spinFor(9 * holdTime); 101 holdFor(myLock, holdTime); 102 } 103 } 104 private boolean sharedLock; 105 private int holdTime; 106 } 107 108 private class SleepyLockHolder implements Runnable { SleepyLockHolder(boolean shared)109 SleepyLockHolder(boolean shared) { 110 sharedLock = shared; 111 } 112 @Override run()113 public void run() { 114 Object myLock = sharedLock ? lock : new Object(); 115 int nIters = TOTAL_ITERS / currentThreadCount / 10_000; 116 for (int i = 0; i < nIters; ++i) { 117 synchronized(myLock) { 118 try { 119 Thread.sleep(2); 120 } catch(InterruptedException e) { 121 throw new AssertionError("Unexpected interrupt"); 122 } 123 counter += 10_000; 124 } 125 } 126 } 127 private boolean sharedLock; 128 } 129 130 // Increment atomicCounter n times, on average by 1 each time. 131 private class RepeatedIncrementer implements Runnable { 132 @Override run()133 public void run() { 134 int y = -1; 135 int nIters = TOTAL_ITERS / currentThreadCount; 136 for (int i = 0; i < nIters; ++i) { 137 atomicCounter.addAndGet(y); 138 y = nextInt(y); 139 } 140 } 141 } 142 143 // Run n threads doing work. Return the elapsed time this took, in milliseconds. runMultiple(int n, Runnable work)144 private long runMultiple(int n, Runnable work) { 145 Thread[] threads = new Thread[n]; 146 // Replace lock, so that we start with a clean, uninflated lock each time. 147 lock = new Object(); 148 for (int i = 0; i < n; ++i) { 149 threads[i] = new Thread(work); 150 } 151 long startTime = System.currentTimeMillis(); 152 for (int i = 0; i < n; ++i) { 153 threads[i].start(); 154 } 155 for (int i = 0; i < n; ++i) { 156 try { 157 threads[i].join(); 158 } catch(InterruptedException e) { 159 throw new AssertionError("Unexpected interrupt"); 160 } 161 } 162 return System.currentTimeMillis() - startTime; 163 } 164 165 // Run on different numbers of threads. runAll(Runnable work, Runnable init, Runnable checker)166 private void runAll(Runnable work, Runnable init, Runnable checker) { 167 for (int i = 1; i <= 8; i *= 2) { 168 currentThreadCount = i; 169 init.run(); 170 long time = runMultiple(i, work); 171 if (PRINT_TIMES) { 172 System.out.print(time + (i == 8 ? "\n" : "\t")); 173 } 174 checker.run(); 175 } 176 } 177 178 private class CheckAtomicCounter implements Runnable { 179 @Override run()180 public void run() { 181 if (atomicCounter.get() != TOTAL_ITERS) { 182 throw new AssertionError("Failed atomicCounter postcondition check for " 183 + currentThreadCount + " threads"); 184 } 185 } 186 } 187 188 private class CheckCounter implements Runnable { 189 private final int expected; CheckCounter(int e)190 public CheckCounter(int e) { 191 expected = e; 192 } 193 @Override run()194 public void run() { 195 if (counter != expected) { 196 throw new AssertionError("Failed counter postcondition check for " 197 + currentThreadCount + " threads, expected " + expected + " got " + counter); 198 } 199 } 200 } 201 run()202 private void run() { 203 if (PRINT_TIMES) { 204 System.out.println("All times in milliseconds for 1, 2, 4 and 8 threads"); 205 } 206 System.out.println("Atomic increments"); 207 runAll(new RepeatedIncrementer(), () -> { atomicCounter.set(0); }, new CheckAtomicCounter()); 208 for (int i = 2; i <= MAX_HOLD_TIME; i *= 10) { 209 // i * 8 (max thread count) divides TOTAL_ITERS 210 System.out.println("Hold time " + i + ", shared lock"); 211 runAll(new RepeatedLockHolder(true, i), () -> { counter = 0; }, 212 new CheckCounter(TOTAL_ITERS)); 213 } 214 for (int i = 2; i <= MAX_HOLD_TIME / 10; i *= 10) { 215 // i * 8 (max thread count) divides TOTAL_ITERS 216 System.out.println("Hold time " + i + ", pause time " + (9 * i) + ", shared lock"); 217 runAll(new RepeatedIntermittentLockHolder(true, i), () -> { counter = 0; }, 218 new CheckCounter(TOTAL_ITERS / 10)); 219 } 220 if (PRINT_TIMES) { 221 for (int i = 2; i <= MAX_HOLD_TIME; i *= 1000) { 222 // i divides TOTAL_ITERS 223 System.out.println("Hold time " + i + ", private lock"); 224 // Since there is no mutual exclusion final counter value is unpredictable. 225 runAll(new RepeatedLockHolder(false, i), () -> { counter = 0; }, () -> {}); 226 } 227 } 228 System.out.println("Hold for 2 msecs while sleeping, shared lock"); 229 runAll(new SleepyLockHolder(true), () -> { counter = 0; }, new CheckCounter(TOTAL_ITERS)); 230 System.out.println("Hold for 2 msecs while sleeping, private lock"); 231 runAll(new SleepyLockHolder(false), () -> { counter = 0; }, () -> {}); 232 } 233 main(String[] args)234 public static void main(String[] args) { 235 System.out.println("Starting"); 236 new Main().run(); 237 } 238 } 239