1 /* 2 * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 25 /* 26 * @test 27 * @bug 6969574 28 * 29 * @summary converted from VM Testbase vm/mlvm/mixed/stress/regression/b6969574. 30 * VM Testbase keywords: [feature_mlvm, nonconcurrent] 31 * 32 * @library /vmTestbase 33 * /test/lib 34 * @run driver jdk.test.lib.FileInstaller . . 35 * 36 * @comment build test class and indify classes 37 * @build vm.mlvm.mixed.stress.regression.b6969574.INDIFY_Test 38 * @run driver vm.mlvm.share.IndifiedClassesBuilder 39 * 40 * @run main/othervm vm.mlvm.mixed.stress.regression.b6969574.INDIFY_Test 41 */ 42 43 package vm.mlvm.mixed.stress.regression.b6969574; 44 45 import java.lang.invoke.CallSite; 46 import java.lang.invoke.ConstantCallSite; 47 import java.lang.invoke.MethodHandle; 48 import java.lang.invoke.MethodHandles; 49 import java.lang.invoke.MethodType; 50 import java.lang.reflect.Method; 51 import java.util.LinkedList; 52 53 import vm.mlvm.share.Env; 54 import vm.mlvm.share.MlvmTest; 55 import vm.share.options.Option; 56 57 /** 58 * Test for CR 6969574: Verify that MethodHandles is faster than reflection and comparable 59 * in order of magnitude to direct calls. 60 * The test is supposed to run in -Xcomp/-Xmixed modes. 61 * It can fail in -Xint. 62 63 */ 64 65 public class INDIFY_Test extends MlvmTest { 66 67 @Option(name="warmups", default_value="5", description="Number of warm-up cycles") 68 private int warmups; 69 70 @Option(name="measurements", default_value="10", description="Number of test run cycles") 71 private int measurements; 72 73 @Option(name="iterations", default_value="1000000", description="Number iterations per test run") 74 private int iterations; 75 76 @Option(name="micro.iterations", default_value="5", description="Number micro-iterations per iteration") 77 private int microIterations; 78 79 private static final int MICRO_TO_NANO = 1000000; 80 81 private static final String TESTEE_ARG2 = "abc"; 82 private static final long TESTEE_ARG3 = 123; 83 84 // 85 // Test method and its stuff 86 // 87 private static int sMicroIterations; 88 89 private static class TestData { 90 int i; 91 } 92 93 private static final String TESTEE_METHOD_NAME = "testee"; 94 95 static long testee; 96 /** 97 * A testee method. Declared public due to Reflection API requirements. 98 * Not intended for external use. 99 */ 100 public static void testee(TestData d, String y, long x) { 101 for (int i = 0; i < INDIFY_Test.sMicroIterations; i++) { 102 testee /= 1 + (d.i | 1); 103 } 104 } 105 106 // 107 // Indify stubs for invokedynamic 108 // 109 private static MethodType MT_bootstrap() { 110 return MethodType.methodType(Object.class, Object.class, Object.class, Object.class); 111 } 112 113 private static MethodHandle MH_bootstrap() throws NoSuchMethodException, IllegalAccessException { 114 return MethodHandles.lookup().findStatic(INDIFY_Test.class, "bootstrap", MT_bootstrap()); 115 } 116 117 private static MethodType MT_target() { 118 return MethodType.methodType(void.class, TestData.class, String.class, long.class); 119 } 120 121 private static MethodHandle INDY_call; 122 private static MethodHandle INDY_call() throws Throwable { 123 if (INDY_call != null) { 124 return INDY_call; 125 } 126 127 return ((CallSite) MH_bootstrap().invokeWithArguments(MethodHandles.lookup(), "hello", MT_target())).dynamicInvoker(); 128 } 129 130 private static Object bootstrap(Object l, Object n, Object t) throws Throwable { 131 trace("BSM called"); 132 return new ConstantCallSite(MethodHandles.lookup().findStatic(INDIFY_Test.class, TESTEE_METHOD_NAME, MT_target())); 133 } 134 135 // The function below contains invokedynamic instruction after processing 136 // with Indify 137 private static void indyWrapper(TestData d) throws Throwable { 138 INDY_call().invokeExact(d, TESTEE_ARG2, TESTEE_ARG3); 139 } 140 141 // 142 // Benchmarking infrastructure 143 // 144 private abstract static class T { 145 public abstract void run() throws Throwable; 146 } 147 148 private static class Measurement { 149 Benchmark benchmark; 150 long time; 151 long iterations; 152 double timePerIteration; 153 154 Measurement(Benchmark b, long t, long iter) { 155 benchmark = b; 156 time = t; 157 iterations = iter; 158 timePerIteration = (double) time / iterations; 159 } 160 161 void report(Measurement compareToThis) { 162 String line = String.format("%40s: %7.1f ns", benchmark.name, timePerIteration * MICRO_TO_NANO); 163 164 if (compareToThis != null && compareToThis != this) { 165 double ratio = (double) timePerIteration / compareToThis.timePerIteration; 166 String er = "slower"; 167 168 if (ratio < 1) { 169 er = "FASTER"; 170 ratio = 1 / ratio; 171 } 172 173 line += String.format(" // %.1f times %s than %s", ratio, er, compareToThis.benchmark.name); 174 } 175 176 print(line); 177 } 178 } 179 180 private static class Result { 181 Benchmark benchmark; 182 double mean; 183 double stdDev; 184 185 public Result(Benchmark b, double mean, double stdDev) { 186 benchmark = b; 187 this.mean = mean; 188 this.stdDev = stdDev; 189 } 190 191 public void report(Result compareToThis) { 192 String line = String.format( 193 "%40s: %7.1f ns (stddev: %5.1f = %2d%%)", 194 benchmark.name, 195 mean * MICRO_TO_NANO, 196 stdDev * MICRO_TO_NANO, 197 (int) (100 * stdDev / mean)); 198 199 if (compareToThis != null && compareToThis != this) { 200 double ratio = mean / compareToThis.mean; 201 String er = "slower"; 202 203 if (ratio < 1) { 204 er = "FASTER"; 205 ratio = 1 / ratio; 206 } 207 208 line += String.format(" // %.1f times %s than %s", ratio, er, compareToThis.benchmark.name); 209 } 210 211 print(line); 212 } 213 214 public static Result calculate(Measurement[] measurements, Result substractThis) { 215 if (measurements.length == 0) { 216 throw new IllegalArgumentException("No measurements!"); 217 } 218 219 double meanToSubstract = 0; 220 if (substractThis != null) { 221 meanToSubstract = substractThis.mean; 222 } 223 224 long timeSum = 0; 225 long iterationsSum = 0; 226 for (Measurement m : measurements) { 227 timeSum += m.time; 228 iterationsSum += m.iterations; 229 } 230 231 double mean = (double) timeSum / iterationsSum - meanToSubstract; 232 233 double stdDev = 0; 234 for (Measurement m : measurements) { 235 double result = (double) m.time / m.iterations - meanToSubstract; 236 stdDev += Math.pow(result - mean, 2); 237 } 238 stdDev = Math.sqrt(stdDev / measurements.length); 239 240 return new Result(measurements[0].benchmark, mean, stdDev); 241 } 242 243 public String getMeanStr() { 244 return String.format("%.1f ns", mean * MICRO_TO_NANO); 245 } 246 247 public Benchmark getBenchmark() { 248 return benchmark; 249 } 250 } 251 252 private static class Benchmark { 253 String name; 254 T runnable; 255 LinkedList<Measurement> runResults = new LinkedList<Measurement>(); 256 257 public Benchmark(String name, T runnable) { 258 this.name = name; 259 this.runnable = runnable; 260 } 261 262 public Measurement run(int iterations, boolean warmingUp) throws Throwable { 263 long start = System.currentTimeMillis(); 264 265 for (int i = iterations; i > 0; --i) { 266 runnable.run(); 267 } 268 269 long duration = System.currentTimeMillis() - start; 270 271 Measurement measurement = new Measurement(this, duration, iterations); 272 273 if (!warmingUp) { 274 runResults.add(measurement); 275 } 276 277 return measurement; 278 } 279 280 public void shortWarmup() throws Throwable { 281 runnable.run(); 282 } 283 284 public String getName() { 285 return name; 286 } 287 } 288 289 private static double relativeOrder(double value, double base) { 290 return Math.log10(Math.abs(value - base) / base); 291 } 292 293 private void verifyTimeOrder(Result value, Result base) { 294 double timeOrder = relativeOrder(value.mean, base.mean); 295 296 if (timeOrder > 1) { 297 markTestFailed(value.getBenchmark().getName() + " invocation time order (" 298 + value.getMeanStr() 299 + ") is greater than of " + base.getBenchmark().getName() + "(" 300 + base.getMeanStr() + ")!"); 301 } 302 303 print(value.getBenchmark().getName() 304 + " <= " 305 + base.getBenchmark().getName() 306 + ": Good."); 307 } 308 309 // The numbers below are array indexes + size of array (the last constant). 310 // They should be consecutive, starting with 0 311 private final static int DIRECT_CALL = 0; 312 private final static int REFLECTION_CALL = 1; 313 private final static int INVOKE_EXACT = 2; 314 private final static int INVOKE = 3; 315 private final static int INDY = 4; 316 private final static int BENCHMARK_COUNT = 5; 317 318 // 319 // Test body 320 // 321 @Override 322 public boolean run() throws Throwable { 323 sMicroIterations = microIterations; 324 325 final MethodHandle mhTestee = MethodHandles.lookup().findStatic(INDIFY_Test.class, TESTEE_METHOD_NAME, MT_target()); 326 final Method refTestee = getClass().getMethod(TESTEE_METHOD_NAME, new Class<?>[] { TestData.class, String.class, long.class }); 327 328 final TestData testData = new TestData(); 329 330 final Benchmark[] benchmarks = new Benchmark[BENCHMARK_COUNT]; 331 332 benchmarks[DIRECT_CALL] = new Benchmark("Direct call", new T() { 333 public void run() throws Throwable { 334 testee(testData, TESTEE_ARG2, TESTEE_ARG3); 335 } 336 }); 337 338 benchmarks[REFLECTION_CALL] = new Benchmark("Reflection API Method.invoke()", new T() { 339 public void run() throws Throwable { 340 refTestee.invoke(null, testData, TESTEE_ARG2, TESTEE_ARG3); 341 } 342 }); 343 344 benchmarks[INVOKE_EXACT] = new Benchmark("MH.invokeExact()", new T() { 345 public void run() throws Throwable { 346 mhTestee.invokeExact(testData, TESTEE_ARG2, TESTEE_ARG3); 347 } 348 }); 349 350 benchmarks[INVOKE] = new Benchmark("MH.invoke()", new T() { 351 public void run() throws Throwable { 352 mhTestee.invokeExact(testData, TESTEE_ARG2, TESTEE_ARG3); 353 } 354 }); 355 356 benchmarks[INDY] = new Benchmark("invokedynamic instruction", new T() { 357 public void run() throws Throwable { 358 indyWrapper(testData); 359 } 360 }); 361 362 for (int w = 0; w < warmups; w++) { 363 trace("\n======== Warming up, iteration #" + w); 364 365 for (int i = iterations; i > 0; i--) { 366 for (int r = 0; r < benchmarks.length; r++) 367 benchmarks[r].shortWarmup(); 368 } 369 } 370 371 final int compareToIdx = REFLECTION_CALL; 372 for (int i = 0; i < measurements; i++) { 373 trace("\n======== Measuring, iteration #" + i); 374 375 for (int r = 0; r < benchmarks.length; r++) { 376 benchmarks[r].run(iterations, false).report( 377 r > compareToIdx ? benchmarks[compareToIdx].runResults.getLast() : null); 378 } 379 } 380 381 final Result[] results = new Result[benchmarks.length]; 382 383 print("\n======== Results (absolute)" + "; warmups: " + warmups 384 + "; measurements: " + measurements + "; iterations/run: " + iterations 385 + "; micro iterations: " + microIterations); 386 387 for (int r = 0; r < benchmarks.length; r++) { 388 results[r] = Result.calculate(benchmarks[r].runResults.toArray(new Measurement[0]), null); 389 } 390 391 for (int r = 0; r < benchmarks.length; r++) { 392 results[r].report(r != compareToIdx ? results[compareToIdx] : null); 393 } 394 395 print("\n======== Conclusions"); 396 397 // TODO: exclude GC time, compilation time (optionally) from measurements 398 399 print("Comparing invocation time orders"); 400 verifyTimeOrder(results[REFLECTION_CALL], results[INVOKE_EXACT]); 401 verifyTimeOrder(results[INVOKE_EXACT], results[DIRECT_CALL]); 402 verifyTimeOrder(results[INVOKE], results[DIRECT_CALL]); 403 verifyTimeOrder(results[INVOKE_EXACT], results[INDY]); 404 405 return true; 406 } 407 408 // Below are routines for converting this test to a standalone one 409 // This is useful if you want to run the test with JDK7 b103 release 410 // where the regression can be seen 411 static void print(String s) { 412 Env.traceImportant(s); 413 } 414 415 static void trace(String s) { 416 Env.traceNormal(s); 417 } 418 419 //boolean testFailed; 420 //static void markTestFailed(String reason) { 421 // testFailed = true; 422 //} 423 424 public static void main(String[] args) { 425 MlvmTest.launch(args); 426 } 427 }