1 /* 2 * Copyright (C) 2017 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 17 import java.lang.reflect.Method; 18 import java.util.ArrayList; 19 import java.util.List; 20 import java.util.Random; 21 import java.util.concurrent.atomic.AtomicInteger; 22 import java.util.concurrent.Callable; 23 import java.util.concurrent.CyclicBarrier; 24 import java.util.concurrent.ExecutionException; 25 import java.util.concurrent.ExecutorService; 26 import java.util.concurrent.Future; 27 import java.util.concurrent.LinkedBlockingQueue; 28 import java.util.concurrent.ThreadPoolExecutor; 29 import java.util.concurrent.TimeUnit; 30 31 /** 32 * A test driver for JIT compiling methods and looking for JIT 33 * cache issues. 34 */ 35 public class JitCacheChurnTest { 36 /* The name of methods to JIT */ 37 private static final String JITTED_METHOD = "$noinline$Call"; 38 39 /* The number of cores to oversubscribe load by. */ 40 private static final int OVERSUBSCRIBED_CORES = 1; 41 42 /* The number of concurrent executions of methods to be JIT compiled. */ 43 private static final int CONCURRENCY = 44 Runtime.getRuntime().availableProcessors() + OVERSUBSCRIBED_CORES; 45 46 /* The number of times the methods to be JIT compiled should be executed per thread. */ 47 private static final int METHOD_ITERATIONS = 10; 48 49 /* Number of test iterations JIT methods and removing methods from JIT cache. */ 50 private static final int TEST_ITERATIONS = 512; 51 52 /* Tasks to run and generate compiled code of various sizes */ 53 private static final BaseTask [] TASKS = { 54 new TaskOne(), new TaskTwo(), new TaskThree(), new TaskFour(), new TaskFive(), new TaskSix(), 55 new TaskSeven(), new TaskEight(), new TaskNine(), new TaskTen() 56 }; 57 private static final int TASK_BITMASK = (1 << TASKS.length) - 1; 58 59 private final ExecutorService executorService; 60 private int runMask = 0; 61 62 private JitCacheChurnTest() { 63 this.executorService = new ThreadPoolExecutor(CONCURRENCY, CONCURRENCY, 5000, 64 TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>()); 65 } 66 67 private void shutdown() { 68 this.executorService.shutdown(); 69 } 70 71 private void runTasks(Callable<Integer> task) { 72 // Force JIT compilation of tasks method. 73 ensureJitCompiled(task.getClass(), JITTED_METHOD); 74 75 // Launch worker threads to run JIT compiled method. 76 try { 77 ArrayList<Callable<Integer>> tasks = new ArrayList<>(CONCURRENCY); 78 for (int i = 0; i < CONCURRENCY; ++i) { 79 tasks.add(i, task); 80 } 81 82 List<Future<Integer>> results = executorService.invokeAll(tasks); 83 for (Future<?> result : results) { 84 result.get(); 85 } 86 } catch (InterruptedException | ExecutionException e) { 87 System.err.println(e); 88 System.exit(-1); 89 } 90 } 91 92 private static abstract class BaseTask implements Callable<Integer> { 93 private static CyclicBarrier barrier = new CyclicBarrier(CONCURRENCY); 94 95 public Integer call() throws Exception { 96 barrier.await(); 97 int iterations = METHOD_ITERATIONS + 1; 98 for (int i = 0; i < iterations; ++i) { 99 $noinline$Call(); 100 } 101 return $noinline$Call(); 102 } 103 104 protected abstract Integer $noinline$Call(); 105 } 106 107 private static class TaskOne extends BaseTask { 108 @Override 109 protected Integer $noinline$Call() { 110 return null; 111 } 112 } 113 114 private static class TaskTwo extends BaseTask { 115 @Override 116 protected Integer $noinline$Call() { 117 return 0; 118 } 119 } 120 121 private static class TaskThree extends BaseTask { 122 @Override 123 protected Integer $noinline$Call() { 124 int sum = 0; 125 for (int i = 0; i < 3; ++i) { 126 sum = i * (i + 1); 127 } 128 return sum; 129 } 130 } 131 132 private static class TaskFour extends BaseTask { 133 @Override 134 protected Integer $noinline$Call() { 135 int sum = 0; 136 for (int i = 0; i < 10; ++i) { 137 int bits = i; 138 bits = ((bits >>> 1) & 0x55555555) | ((bits << 1) & 0x55555555); 139 bits = ((bits >>> 2) & 0x33333333) | ((bits << 2) & 0x33333333); 140 bits = ((bits >>> 4) & 0x0f0f0f0f) | ((bits << 4) & 0x0f0f0f0f); 141 bits = ((bits >>> 8) & 0x00ff00ff) | ((bits << 8) & 0x00ff00ff); 142 bits = (bits >>> 16) | (bits << 16); 143 sum += bits; 144 } 145 return sum; 146 } 147 } 148 149 private static class TaskFive extends BaseTask { 150 static final AtomicInteger instances = new AtomicInteger(0); 151 int instance; 152 TaskFive() { 153 instance = instances.getAndIncrement(); 154 } 155 protected Integer $noinline$Call() { 156 return instance; 157 } 158 } 159 160 private static class TaskSix extends TaskFive { 161 protected Integer $noinline$Call() { 162 return instance + 1; 163 } 164 } 165 166 private static class TaskSeven extends TaskFive { 167 protected Integer $noinline$Call() { 168 return 2 * instance + 1; 169 } 170 } 171 172 private static class TaskEight extends TaskFive { 173 protected Integer $noinline$Call() { 174 double a = Math.cosh(2.22 * instance); 175 double b = a / 2; 176 double c = b * 3; 177 double d = a + b + c; 178 if (d > 42) { 179 d *= Math.max(Math.sin(d), Math.sinh(d)); 180 d *= Math.max(1.33, 0.17 * Math.sinh(d)); 181 d *= Math.max(1.34, 0.21 * Math.sinh(d)); 182 d *= Math.max(1.35, 0.32 * Math.sinh(d)); 183 d *= Math.max(1.36, 0.41 * Math.sinh(d)); 184 d *= Math.max(1.37, 0.57 * Math.sinh(d)); 185 d *= Math.max(1.38, 0.61 * Math.sinh(d)); 186 d *= Math.max(1.39, 0.79 * Math.sinh(d)); 187 d += Double.parseDouble("3.711e23"); 188 } 189 190 if (d > 3) { 191 return (int) a; 192 } else { 193 return (int) b; 194 } 195 } 196 } 197 198 private static class TaskNine extends TaskFive { 199 private final String [] numbers = { "One", "Two", "Three", "Four", "Five", "Six" }; 200 201 protected Integer $noinline$Call() { 202 String number = numbers[instance % numbers.length]; 203 return number.length(); 204 } 205 } 206 207 private static class TaskTen extends TaskFive { 208 private final String [] numbers = { "12345", "23451", "34512", "78901", "89012" }; 209 210 protected Integer $noinline$Call() { 211 int odd = 0; 212 String number = numbers[instance % numbers.length]; 213 for (int i = 0; i < number.length(); i += 2) { 214 odd += Integer.parseInt(numbers[i]); 215 } 216 odd *= 3; 217 218 int even = 0; 219 for (int i = 1; i < number.length(); i += 2) { 220 even += Integer.parseInt(numbers[i]); 221 } 222 return (odd + even) % 10; 223 } 224 } 225 226 private void runAndJitMethods(int mask) { 227 runMask |= mask; 228 for (int index = 0; mask != 0; mask >>= 1, index++) { 229 if ((mask & 1) == 1) { 230 runTasks(TASKS[index]); 231 } 232 } 233 } 234 235 private static void ensureJitCompiled(Class<?> klass, String name) { 236 Main.ensureJitCompiled(klass, name); 237 } 238 239 private void removeJittedMethod(Class<?> klass, String name) { 240 Method method = null; 241 try { 242 method = klass.getDeclaredMethod(name); 243 } catch (NoSuchMethodException e) { 244 System.err.println(e); 245 System.exit(-1); 246 } 247 removeJitCompiledMethod(method, false); 248 } 249 250 private void removeJittedMethods(int mask) { 251 mask = mask & runMask; 252 runMask ^= mask; 253 for (int index = 0; mask != 0; mask >>= 1, index++) { 254 if ((mask & 1) == 1) { 255 removeJittedMethod(TASKS[index].getClass(), JITTED_METHOD); 256 } 257 } 258 } 259 260 private static int getMethodsAsMask(Random rng) { 261 return rng.nextInt(TASK_BITMASK) + 1; 262 } 263 264 public static void run() { 265 JitCacheChurnTest concurrentExecution = new JitCacheChurnTest(); 266 Random invokeMethodGenerator = new Random(5); 267 Random removeMethodGenerator = new Random(7); 268 try { 269 for (int i = 0; i < TEST_ITERATIONS; ++i) { 270 concurrentExecution.runAndJitMethods(getMethodsAsMask(invokeMethodGenerator)); 271 concurrentExecution.removeJittedMethods(getMethodsAsMask(removeMethodGenerator)); 272 } 273 } finally { 274 concurrentExecution.shutdown(); 275 } 276 } 277 278 private static native void removeJitCompiledMethod(Method method, boolean releaseMemory); 279 } 280