/*
|
* 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);
|
}
|