/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.util.concurrent.atomic.AtomicInteger; public class Main { private final boolean PRINT_TIMES = false; // False for use as run test. // Number of increments done by each thread. Must be multiple of largest hold time below, // times any possible thread count. Finishes much faster when used as run test. private final int TOTAL_ITERS = PRINT_TIMES? 16_000_000 : 1_600_000; private final int MAX_HOLD_TIME = PRINT_TIMES? 2_000_000 : 200_000; private int counter; private AtomicInteger atomicCounter = new AtomicInteger(); private Object lock; private int currentThreadCount = 0; // A function such that if we repeatedly apply it to -1, the value oscillates // between -1 and 3. Thus the average value is 1. // This is designed to make it hard for the compiler to predict the values in // the sequence. private int nextInt(int x) { if (x < 0) { return x * x + 2; } else { return x - 4; } } // Increment counter by n, holding lock for time roughly propertional to n. // N must be even. private void holdFor(Object lock, int n) { synchronized(lock) { int y = -1; for (int i = 0; i < n; ++i) { counter += y; y = nextInt(y); } } } // Increment local by an even number n in a way that takes time roughly proportional // to n. private void spinFor(int n) { int y = -1; int local_counter = 0; for (int i = 0; i < n; ++i) { local_counter += y; y = nextInt(y); } if (local_counter != n) { throw new Error(); } } private class RepeatedLockHolder implements Runnable { RepeatedLockHolder(boolean shared, int n /* even */) { sharedLock = shared; holdTime = n; } @Override public void run() { Object myLock = sharedLock ? lock : new Object(); int nIters = TOTAL_ITERS / currentThreadCount / holdTime; for (int i = 0; i < nIters; ++i) { holdFor(myLock, holdTime); } } private boolean sharedLock; private int holdTime; } private class RepeatedIntermittentLockHolder implements Runnable { RepeatedIntermittentLockHolder(boolean shared, int n /* even */) { sharedLock = shared; holdTime = n; } @Override public void run() { Object myLock = sharedLock ? lock : new Object(); int nIters = TOTAL_ITERS / 10 / currentThreadCount / holdTime; for (int i = 0; i < nIters; ++i) { spinFor(9 * holdTime); holdFor(myLock, holdTime); } } private boolean sharedLock; private int holdTime; } private class SleepyLockHolder implements Runnable { SleepyLockHolder(boolean shared) { sharedLock = shared; } @Override public void run() { Object myLock = sharedLock ? lock : new Object(); int nIters = TOTAL_ITERS / currentThreadCount / 10_000; for (int i = 0; i < nIters; ++i) { synchronized(myLock) { try { Thread.sleep(2); } catch(InterruptedException e) { throw new AssertionError("Unexpected interrupt"); } counter += 10_000; } } } private boolean sharedLock; } // Increment atomicCounter n times, on average by 1 each time. private class RepeatedIncrementer implements Runnable { @Override public void run() { int y = -1; int nIters = TOTAL_ITERS / currentThreadCount; for (int i = 0; i < nIters; ++i) { atomicCounter.addAndGet(y); y = nextInt(y); } } } // Run n threads doing work. Return the elapsed time this took, in milliseconds. private long runMultiple(int n, Runnable work) { Thread[] threads = new Thread[n]; // Replace lock, so that we start with a clean, uninflated lock each time. lock = new Object(); for (int i = 0; i < n; ++i) { threads[i] = new Thread(work); } long startTime = System.currentTimeMillis(); for (int i = 0; i < n; ++i) { threads[i].start(); } for (int i = 0; i < n; ++i) { try { threads[i].join(); } catch(InterruptedException e) { throw new AssertionError("Unexpected interrupt"); } } return System.currentTimeMillis() - startTime; } // Run on different numbers of threads. private void runAll(Runnable work, Runnable init, Runnable checker) { for (int i = 1; i <= 8; i *= 2) { currentThreadCount = i; init.run(); long time = runMultiple(i, work); if (PRINT_TIMES) { System.out.print(time + (i == 8 ? "\n" : "\t")); } checker.run(); } } private class CheckAtomicCounter implements Runnable { @Override public void run() { if (atomicCounter.get() != TOTAL_ITERS) { throw new AssertionError("Failed atomicCounter postcondition check for " + currentThreadCount + " threads"); } } } private class CheckCounter implements Runnable { private final int expected; public CheckCounter(int e) { expected = e; } @Override public void run() { if (counter != expected) { throw new AssertionError("Failed counter postcondition check for " + currentThreadCount + " threads, expected " + expected + " got " + counter); } } } private void run() { if (PRINT_TIMES) { System.out.println("All times in milliseconds for 1, 2, 4 and 8 threads"); } System.out.println("Atomic increments"); runAll(new RepeatedIncrementer(), () -> { atomicCounter.set(0); }, new CheckAtomicCounter()); for (int i = 2; i <= MAX_HOLD_TIME; i *= 10) { // i * 8 (max thread count) divides TOTAL_ITERS System.out.println("Hold time " + i + ", shared lock"); runAll(new RepeatedLockHolder(true, i), () -> { counter = 0; }, new CheckCounter(TOTAL_ITERS)); } for (int i = 2; i <= MAX_HOLD_TIME / 10; i *= 10) { // i * 8 (max thread count) divides TOTAL_ITERS System.out.println("Hold time " + i + ", pause time " + (9 * i) + ", shared lock"); runAll(new RepeatedIntermittentLockHolder(true, i), () -> { counter = 0; }, new CheckCounter(TOTAL_ITERS / 10)); } if (PRINT_TIMES) { for (int i = 2; i <= MAX_HOLD_TIME; i *= 1000) { // i divides TOTAL_ITERS System.out.println("Hold time " + i + ", private lock"); // Since there is no mutual exclusion final counter value is unpredictable. runAll(new RepeatedLockHolder(false, i), () -> { counter = 0; }, () -> {}); } } System.out.println("Hold for 2 msecs while sleeping, shared lock"); runAll(new SleepyLockHolder(true), () -> { counter = 0; }, new CheckCounter(TOTAL_ITERS)); System.out.println("Hold for 2 msecs while sleeping, private lock"); runAll(new SleepyLockHolder(false), () -> { counter = 0; }, () -> {}); } public static void main(String[] args) { System.out.println("Starting"); new Main().run(); } }