/* * 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 task) { // Force JIT compilation of tasks method. ensureJitCompiled(task.getClass(), JITTED_METHOD); // Launch worker threads to run JIT compiled method. try { ArrayList> tasks = new ArrayList<>(CONCURRENCY); for (int i = 0; i < CONCURRENCY; ++i) { tasks.add(i, task); } List> 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 { 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); }