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