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 }