| /* |
| * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| |
| /* |
| * @test |
| * @bug 6969574 |
| * |
| * @summary converted from VM Testbase vm/mlvm/mixed/stress/regression/b6969574. |
| * VM Testbase keywords: [feature_mlvm, nonconcurrent, quarantine] |
| * VM Testbase comments: 8079650 |
| * |
| * @library /vmTestbase |
| * /test/lib |
| * @run driver jdk.test.lib.FileInstaller . . |
| * |
| * @comment build test class and indify classes |
| * @build vm.mlvm.mixed.stress.regression.b6969574.INDIFY_Test |
| * @run driver vm.mlvm.share.IndifiedClassesBuilder |
| * |
| * @run main/othervm vm.mlvm.mixed.stress.regression.b6969574.INDIFY_Test |
| */ |
| |
| package vm.mlvm.mixed.stress.regression.b6969574; |
| |
| import java.lang.invoke.CallSite; |
| import java.lang.invoke.ConstantCallSite; |
| import java.lang.invoke.MethodHandle; |
| import java.lang.invoke.MethodHandles; |
| import java.lang.invoke.MethodType; |
| import java.lang.reflect.Method; |
| import java.util.LinkedList; |
| |
| import vm.mlvm.share.Env; |
| import vm.mlvm.share.MlvmTest; |
| import vm.share.options.Option; |
| |
| /** |
| * Test for CR 6969574: Verify that MethodHandles is faster than reflection and comparable |
| * in order of magnitude to direct calls. |
| * The test is supposed to run in -Xcomp/-Xmixed modes. |
| * It can fail in -Xint. |
| |
| */ |
| |
| public class INDIFY_Test extends MlvmTest { |
| |
| @Option(name="warmups", default_value="5", description="Number of warm-up cycles") |
| private int warmups; |
| |
| @Option(name="measurements", default_value="10", description="Number of test run cycles") |
| private int measurements; |
| |
| @Option(name="iterations", default_value="1000000", description="Number iterations per test run") |
| private int iterations; |
| |
| @Option(name="micro.iterations", default_value="5", description="Number micro-iterations per iteration") |
| private int microIterations; |
| |
| private static final int MICRO_TO_NANO = 1000000; |
| |
| private static final String TESTEE_ARG2 = "abc"; |
| private static final long TESTEE_ARG3 = 123; |
| |
| // |
| // Test method and its stuff |
| // |
| private static int sMicroIterations; |
| |
| private static class TestData { |
| int i; |
| } |
| |
| private static final String TESTEE_METHOD_NAME = "testee"; |
| |
| static long testee; |
| /** |
| * A testee method. Declared public due to Reflection API requirements. |
| * Not intended for external use. |
| */ |
| public static void testee(TestData d, String y, long x) { |
| for (int i = 0; i < INDIFY_Test.sMicroIterations; i++) { |
| testee /= 1 + (d.i | 1); |
| } |
| } |
| |
| // |
| // Indify stubs for invokedynamic |
| // |
| private static MethodType MT_bootstrap() { |
| return MethodType.methodType(Object.class, Object.class, Object.class, Object.class); |
| } |
| |
| private static MethodHandle MH_bootstrap() throws NoSuchMethodException, IllegalAccessException { |
| return MethodHandles.lookup().findStatic(INDIFY_Test.class, "bootstrap", MT_bootstrap()); |
| } |
| |
| private static MethodType MT_target() { |
| return MethodType.methodType(void.class, TestData.class, String.class, long.class); |
| } |
| |
| private static MethodHandle INDY_call; |
| private static MethodHandle INDY_call() throws Throwable { |
| if (INDY_call != null) { |
| return INDY_call; |
| } |
| |
| return ((CallSite) MH_bootstrap().invokeWithArguments(MethodHandles.lookup(), "hello", MT_target())).dynamicInvoker(); |
| } |
| |
| private static Object bootstrap(Object l, Object n, Object t) throws Throwable { |
| trace("BSM called"); |
| return new ConstantCallSite(MethodHandles.lookup().findStatic(INDIFY_Test.class, TESTEE_METHOD_NAME, MT_target())); |
| } |
| |
| // The function below contains invokedynamic instruction after processing |
| // with Indify |
| private static void indyWrapper(TestData d) throws Throwable { |
| INDY_call().invokeExact(d, TESTEE_ARG2, TESTEE_ARG3); |
| } |
| |
| // |
| // Benchmarking infrastructure |
| // |
| private abstract static class T { |
| public abstract void run() throws Throwable; |
| } |
| |
| private static class Measurement { |
| Benchmark benchmark; |
| long time; |
| long iterations; |
| double timePerIteration; |
| |
| Measurement(Benchmark b, long t, long iter) { |
| benchmark = b; |
| time = t; |
| iterations = iter; |
| timePerIteration = (double) time / iterations; |
| } |
| |
| void report(Measurement compareToThis) { |
| String line = String.format("%40s: %7.1f ns", benchmark.name, timePerIteration * MICRO_TO_NANO); |
| |
| if (compareToThis != null && compareToThis != this) { |
| double ratio = (double) timePerIteration / compareToThis.timePerIteration; |
| String er = "slower"; |
| |
| if (ratio < 1) { |
| er = "FASTER"; |
| ratio = 1 / ratio; |
| } |
| |
| line += String.format(" // %.1f times %s than %s", ratio, er, compareToThis.benchmark.name); |
| } |
| |
| print(line); |
| } |
| } |
| |
| private static class Result { |
| Benchmark benchmark; |
| double mean; |
| double stdDev; |
| |
| public Result(Benchmark b, double mean, double stdDev) { |
| benchmark = b; |
| this.mean = mean; |
| this.stdDev = stdDev; |
| } |
| |
| public void report(Result compareToThis) { |
| String line = String.format( |
| "%40s: %7.1f ns (stddev: %5.1f = %2d%%)", |
| benchmark.name, |
| mean * MICRO_TO_NANO, |
| stdDev * MICRO_TO_NANO, |
| (int) (100 * stdDev / mean)); |
| |
| if (compareToThis != null && compareToThis != this) { |
| double ratio = mean / compareToThis.mean; |
| String er = "slower"; |
| |
| if (ratio < 1) { |
| er = "FASTER"; |
| ratio = 1 / ratio; |
| } |
| |
| line += String.format(" // %.1f times %s than %s", ratio, er, compareToThis.benchmark.name); |
| } |
| |
| print(line); |
| } |
| |
| public static Result calculate(Measurement[] measurements, Result substractThis) { |
| if (measurements.length == 0) { |
| throw new IllegalArgumentException("No measurements!"); |
| } |
| |
| double meanToSubstract = 0; |
| if (substractThis != null) { |
| meanToSubstract = substractThis.mean; |
| } |
| |
| long timeSum = 0; |
| long iterationsSum = 0; |
| for (Measurement m : measurements) { |
| timeSum += m.time; |
| iterationsSum += m.iterations; |
| } |
| |
| double mean = (double) timeSum / iterationsSum - meanToSubstract; |
| |
| double stdDev = 0; |
| for (Measurement m : measurements) { |
| double result = (double) m.time / m.iterations - meanToSubstract; |
| stdDev += Math.pow(result - mean, 2); |
| } |
| stdDev = Math.sqrt(stdDev / measurements.length); |
| |
| return new Result(measurements[0].benchmark, mean, stdDev); |
| } |
| |
| public String getMeanStr() { |
| return String.format("%.1f ns", mean * MICRO_TO_NANO); |
| } |
| |
| public Benchmark getBenchmark() { |
| return benchmark; |
| } |
| } |
| |
| private static class Benchmark { |
| String name; |
| T runnable; |
| LinkedList<Measurement> runResults = new LinkedList<Measurement>(); |
| |
| public Benchmark(String name, T runnable) { |
| this.name = name; |
| this.runnable = runnable; |
| } |
| |
| public Measurement run(int iterations, boolean warmingUp) throws Throwable { |
| long start = System.currentTimeMillis(); |
| |
| for (int i = iterations; i > 0; --i) { |
| runnable.run(); |
| } |
| |
| long duration = System.currentTimeMillis() - start; |
| |
| Measurement measurement = new Measurement(this, duration, iterations); |
| |
| if (!warmingUp) { |
| runResults.add(measurement); |
| } |
| |
| return measurement; |
| } |
| |
| public void shortWarmup() throws Throwable { |
| runnable.run(); |
| } |
| |
| public String getName() { |
| return name; |
| } |
| } |
| |
| private static double relativeOrder(double value, double base) { |
| return Math.log10(Math.abs(value - base) / base); |
| } |
| |
| private void verifyTimeOrder(Result value, Result base) { |
| double timeOrder = relativeOrder(value.mean, base.mean); |
| |
| if (timeOrder > 1) { |
| markTestFailed(value.getBenchmark().getName() + " invocation time order (" |
| + value.getMeanStr() |
| + ") is greater than of " + base.getBenchmark().getName() + "(" |
| + base.getMeanStr() + ")!"); |
| } |
| |
| print(value.getBenchmark().getName() |
| + " <= " |
| + base.getBenchmark().getName() |
| + ": Good."); |
| } |
| |
| // The numbers below are array indexes + size of array (the last constant). |
| // They should be consecutive, starting with 0 |
| private final static int DIRECT_CALL = 0; |
| private final static int REFLECTION_CALL = 1; |
| private final static int INVOKE_EXACT = 2; |
| private final static int INVOKE = 3; |
| private final static int INVOKE_WITHARG = 4; |
| private final static int INVOKE_WITHARG_TYPECONV = 5; |
| private final static int INDY = 6; |
| private final static int BENCHMARK_COUNT = 7; |
| |
| // |
| // Test body |
| // |
| @Override |
| public boolean run() throws Throwable { |
| sMicroIterations = microIterations; |
| |
| final MethodHandle mhTestee = MethodHandles.lookup().findStatic(INDIFY_Test.class, TESTEE_METHOD_NAME, MT_target()); |
| final Method refTestee = getClass().getMethod(TESTEE_METHOD_NAME, new Class<?>[] { TestData.class, String.class, long.class }); |
| |
| final TestData testData = new TestData(); |
| |
| final Benchmark[] benchmarks = new Benchmark[BENCHMARK_COUNT]; |
| |
| benchmarks[DIRECT_CALL] = new Benchmark("Direct call", new T() { |
| public void run() throws Throwable { |
| testee(testData, TESTEE_ARG2, TESTEE_ARG3); |
| } |
| }); |
| |
| benchmarks[REFLECTION_CALL] = new Benchmark("Reflection API Method.invoke()", new T() { |
| public void run() throws Throwable { |
| refTestee.invoke(null, testData, TESTEE_ARG2, TESTEE_ARG3); |
| } |
| }); |
| |
| benchmarks[INVOKE_EXACT] = new Benchmark("MH.invokeExact()", new T() { |
| public void run() throws Throwable { |
| mhTestee.invokeExact(testData, TESTEE_ARG2, TESTEE_ARG3); |
| } |
| }); |
| |
| benchmarks[INVOKE] = new Benchmark("MH.invoke()", new T() { |
| public void run() throws Throwable { |
| mhTestee.invokeExact(testData, TESTEE_ARG2, TESTEE_ARG3); |
| } |
| }); |
| |
| benchmarks[INVOKE_WITHARG] = new Benchmark("MH.invokeWithArguments(), exact types", new T() { |
| public void run() throws Throwable { |
| mhTestee.invokeWithArguments(testData, TESTEE_ARG2, TESTEE_ARG3); |
| } |
| }); |
| |
| benchmarks[INVOKE_WITHARG_TYPECONV] = new Benchmark("MH.invokeWithArguments() + type conv.", new T() { |
| public void run() throws Throwable { |
| mhTestee.invokeWithArguments((Object) testData, null, (Short) Short.MAX_VALUE); |
| } |
| }); |
| |
| benchmarks[INDY] = new Benchmark("invokedynamic instruction", new T() { |
| public void run() throws Throwable { |
| indyWrapper(testData); |
| } |
| }); |
| |
| for (int w = 0; w < warmups; w++) { |
| trace("\n======== Warming up, iteration #" + w); |
| |
| for (int i = iterations; i > 0; i--) { |
| for (int r = 0; r < benchmarks.length; r++) |
| benchmarks[r].shortWarmup(); |
| } |
| } |
| |
| final int compareToIdx = REFLECTION_CALL; |
| for (int i = 0; i < measurements; i++) { |
| trace("\n======== Measuring, iteration #" + i); |
| |
| for (int r = 0; r < benchmarks.length; r++) { |
| benchmarks[r].run(iterations, false).report( |
| r > compareToIdx ? benchmarks[compareToIdx].runResults.getLast() : null); |
| } |
| } |
| |
| final Result[] results = new Result[benchmarks.length]; |
| |
| print("\n======== Results (absolute)" + "; warmups: " + warmups |
| + "; measurements: " + measurements + "; iterations/run: " + iterations |
| + "; micro iterations: " + microIterations); |
| |
| for (int r = 0; r < benchmarks.length; r++) { |
| results[r] = Result.calculate(benchmarks[r].runResults.toArray(new Measurement[0]), null); |
| } |
| |
| for (int r = 0; r < benchmarks.length; r++) { |
| results[r].report(r != compareToIdx ? results[compareToIdx] : null); |
| } |
| |
| print("\n======== Conclusions"); |
| |
| // TODO: exclude GC time, compilation time (optionally) from measurements |
| |
| print("Comparing invocation time orders"); |
| verifyTimeOrder(results[REFLECTION_CALL], results[INVOKE_EXACT]); |
| verifyTimeOrder(results[INVOKE_EXACT], results[DIRECT_CALL]); |
| verifyTimeOrder(results[INVOKE], results[DIRECT_CALL]); |
| verifyTimeOrder(results[INVOKE_WITHARG], results[INVOKE_EXACT]); |
| verifyTimeOrder(results[INVOKE_WITHARG_TYPECONV], results[INVOKE_EXACT]); |
| verifyTimeOrder(results[INVOKE_EXACT], results[INDY]); |
| |
| return true; |
| } |
| |
| // Below are routines for converting this test to a standalone one |
| // This is useful if you want to run the test with JDK7 b103 release |
| // where the regression can be seen |
| static void print(String s) { |
| Env.traceImportant(s); |
| } |
| |
| static void trace(String s) { |
| Env.traceNormal(s); |
| } |
| |
| //boolean testFailed; |
| //static void markTestFailed(String reason) { |
| // testFailed = true; |
| //} |
| |
| public static void main(String[] args) { |
| MlvmTest.launch(args); |
| } |
| } |