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[INDY],                    results[REFLECTION_CALL]);
 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 }