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