/*
* Copyright (C) 2017 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.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.Callable;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* A test driver for JIT compiling methods and looking for JIT
* cache issues.
*/
public class JitCacheChurnTest {
/* The name of methods to JIT */
private static final String JITTED_METHOD = "$noinline$Call";
/* The number of cores to oversubscribe load by. */
private static final int OVERSUBSCRIBED_CORES = 1;
/* The number of concurrent executions of methods to be JIT compiled. */
private static final int CONCURRENCY =
Runtime.getRuntime().availableProcessors() + OVERSUBSCRIBED_CORES;
/* The number of times the methods to be JIT compiled should be executed per thread. */
private static final int METHOD_ITERATIONS = 10;
/* Number of test iterations JIT methods and removing methods from JIT cache. */
private static final int TEST_ITERATIONS = 512;
/* Tasks to run and generate compiled code of various sizes */
private static final BaseTask [] TASKS = {
new TaskOne(), new TaskTwo(), new TaskThree(), new TaskFour(), new TaskFive(), new TaskSix(),
new TaskSeven(), new TaskEight(), new TaskNine(), new TaskTen()
};
private static final int TASK_BITMASK = (1 << TASKS.length) - 1;
private final ExecutorService executorService;
private int runMask = 0;
private JitCacheChurnTest() {
this.executorService = new ThreadPoolExecutor(CONCURRENCY, CONCURRENCY, 5000,
TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>());
}
private void shutdown() {
this.executorService.shutdown();
}
private void runTasks(Callable<Integer> task) {
// Force JIT compilation of tasks method.
ensureJitCompiled(task.getClass(), JITTED_METHOD);
// Launch worker threads to run JIT compiled method.
try {
ArrayList<Callable<Integer>> tasks = new ArrayList<>(CONCURRENCY);
for (int i = 0; i < CONCURRENCY; ++i) {
tasks.add(i, task);
}
List<Future<Integer>> results = executorService.invokeAll(tasks);
for (Future<?> result : results) {
result.get();
}
} catch (InterruptedException | ExecutionException e) {
System.err.println(e);
System.exit(-1);
}
}
private static abstract class BaseTask implements Callable<Integer> {
private static CyclicBarrier barrier = new CyclicBarrier(CONCURRENCY);
public Integer call() throws Exception {
barrier.await();
int iterations = METHOD_ITERATIONS + 1;
for (int i = 0; i < iterations; ++i) {
$noinline$Call();
}
return $noinline$Call();
}
protected abstract Integer $noinline$Call();
}
private static class TaskOne extends BaseTask {
@Override
protected Integer $noinline$Call() {
return null;
}
}
private static class TaskTwo extends BaseTask {
@Override
protected Integer $noinline$Call() {
return 0;
}
}
private static class TaskThree extends BaseTask {
@Override
protected Integer $noinline$Call() {
int sum = 0;
for (int i = 0; i < 3; ++i) {
sum = i * (i + 1);
}
return sum;
}
}
private static class TaskFour extends BaseTask {
@Override
protected Integer $noinline$Call() {
int sum = 0;
for (int i = 0; i < 10; ++i) {
int bits = i;
bits = ((bits >>> 1) & 0x55555555) | ((bits << 1) & 0x55555555);
bits = ((bits >>> 2) & 0x33333333) | ((bits << 2) & 0x33333333);
bits = ((bits >>> 4) & 0x0f0f0f0f) | ((bits << 4) & 0x0f0f0f0f);
bits = ((bits >>> 8) & 0x00ff00ff) | ((bits << 8) & 0x00ff00ff);
bits = (bits >>> 16) | (bits << 16);
sum += bits;
}
return sum;
}
}
private static class TaskFive extends BaseTask {
static final AtomicInteger instances = new AtomicInteger(0);
int instance;
TaskFive() {
instance = instances.getAndIncrement();
}
protected Integer $noinline$Call() {
return instance;
}
}
private static class TaskSix extends TaskFive {
protected Integer $noinline$Call() {
return instance + 1;
}
}
private static class TaskSeven extends TaskFive {
protected Integer $noinline$Call() {
return 2 * instance + 1;
}
}
private static class TaskEight extends TaskFive {
protected Integer $noinline$Call() {
double a = Math.cosh(2.22 * instance);
double b = a / 2;
double c = b * 3;
double d = a + b + c;
if (d > 42) {
d *= Math.max(Math.sin(d), Math.sinh(d));
d *= Math.max(1.33, 0.17 * Math.sinh(d));
d *= Math.max(1.34, 0.21 * Math.sinh(d));
d *= Math.max(1.35, 0.32 * Math.sinh(d));
d *= Math.max(1.36, 0.41 * Math.sinh(d));
d *= Math.max(1.37, 0.57 * Math.sinh(d));
d *= Math.max(1.38, 0.61 * Math.sinh(d));
d *= Math.max(1.39, 0.79 * Math.sinh(d));
d += Double.parseDouble("3.711e23");
}
if (d > 3) {
return (int) a;
} else {
return (int) b;
}
}
}
private static class TaskNine extends TaskFive {
private final String [] numbers = { "One", "Two", "Three", "Four", "Five", "Six" };
protected Integer $noinline$Call() {
String number = numbers[instance % numbers.length];
return number.length();
}
}
private static class TaskTen extends TaskFive {
private final String [] numbers = { "12345", "23451", "34512", "78901", "89012" };
protected Integer $noinline$Call() {
int odd = 0;
String number = numbers[instance % numbers.length];
for (int i = 0; i < number.length(); i += 2) {
odd += Integer.parseInt(numbers[i]);
}
odd *= 3;
int even = 0;
for (int i = 1; i < number.length(); i += 2) {
even += Integer.parseInt(numbers[i]);
}
return (odd + even) % 10;
}
}
private void runAndJitMethods(int mask) {
runMask |= mask;
for (int index = 0; mask != 0; mask >>= 1, index++) {
if ((mask & 1) == 1) {
runTasks(TASKS[index]);
}
}
}
private static void ensureJitCompiled(Class<?> klass, String name) {
Main.ensureJitCompiled(klass, name);
}
private void removeJittedMethod(Class<?> klass, String name) {
Method method = null;
try {
method = klass.getDeclaredMethod(name);
} catch (NoSuchMethodException e) {
System.err.println(e);
System.exit(-1);
}
removeJitCompiledMethod(method, false);
}
private void removeJittedMethods(int mask) {
mask = mask & runMask;
runMask ^= mask;
for (int index = 0; mask != 0; mask >>= 1, index++) {
if ((mask & 1) == 1) {
removeJittedMethod(TASKS[index].getClass(), JITTED_METHOD);
}
}
}
private static int getMethodsAsMask(Random rng) {
return rng.nextInt(TASK_BITMASK) + 1;
}
public static void run() {
JitCacheChurnTest concurrentExecution = new JitCacheChurnTest();
Random invokeMethodGenerator = new Random(5);
Random removeMethodGenerator = new Random(7);
try {
for (int i = 0; i < TEST_ITERATIONS; ++i) {
concurrentExecution.runAndJitMethods(getMethodsAsMask(invokeMethodGenerator));
concurrentExecution.removeJittedMethods(getMethodsAsMask(removeMethodGenerator));
}
} finally {
concurrentExecution.shutdown();
}
}
private static native void removeJitCompiledMethod(Method method, boolean releaseMemory);
}