是否可以阻止JIT优化远离方法调用?

时间:2012-08-29 10:45:30

标签: java jit compiler-optimization

我们正在构建一个用于Java字节代码程序的平均案例运行时分析的工具。其中一部分是测量实际运行时间。因此,我们将采用一个任意的,用户提供的方法,可能有也可能没有结果,可能有也可能没有副作用(例子包括Quicksort,factorial,dummy嵌套循环......)并执行它(使用反射),测量经过的时间。 (无论我们是否正确地进行基准测试,除了这里的要点之外。)

在基准测试代码中,我们显然不对结果做任何事情(有些方法甚至没有结果)。因此,没有人知道JIT可能会做什么,事实上我们已经观察到它似乎有时会优化整个基准测试方法。由于基准测试方法实际上没有单独使用,这使得基准测试无用。

我们如何防止JIT这样做?我们不想完全关闭它,因为基准测试需要很长时间,而且我们想要对“真正的”运行时进行基准测试(因此我们希望JIT在内部方法中运行)。

我知道this question但是给定的场景太窄了;我们不知道结果类型(如果有的话),因此不能以某种方式使用结果,JIT看起来没用。

3 个答案:

答案 0 :(得分:2)

简单的解决方案是编写一个更现实的基准测试,它可以做一些非常有用的事情,因此不会对其进行优化。

有很多技巧可以混淆JIT,但这些不太可能对你有所帮助。

以下是基准测试的示例,其中通过反射,MethodHandle调用该方法并将其编译为空。

import java.lang.invoke.*;
import java.lang.reflect.*;

public class Main {
    public static void main(String... args) throws Throwable {
        for (int j = 0; j < 5; j++) {
            testViaReflection();
            testViaMethodHandle();
            testWithoutReflection();
        }
    }

    private static void testViaReflection() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Method nothing = Main.class.getDeclaredMethod("nothing");
        int runs = 10000000; // triggers a warmup.
        long start = System.nanoTime();
        Object[] args = new Object[0];
        for (int i = 0; i < runs; i++)
            nothing.invoke(null, args);
        long time = System.nanoTime() - start;
        System.out.printf("A call to %s took an average of %.1f ns using reflection%n", nothing.getName(), 1.0 * time / runs);
    }

    private static void testViaMethodHandle() throws Throwable {
        MethodHandle nothing = MethodHandles.lookup().unreflect(Main.class.getDeclaredMethod("nothing"));
        int runs = 10000000; // triggers a warmup.
        long start = System.nanoTime();
        for (int i = 0; i < runs; i++) {
            nothing.invokeExact();
        }
        long time = System.nanoTime() - start;
        System.out.printf("A call to %s took an average of %.1f ns using MethodHandle%n", "nothing", 1.0 * time / runs);
    }

    private static void testWithoutReflection() {
        int runs = 10000000; // triggers a warmup.
        long start = System.nanoTime();
        for (int i = 0; i < runs; i++)
            nothing();
        long time = System.nanoTime() - start;
        System.out.printf("A call to %s took an average of %.1f ns without reflection%n", "nothing", 1.0 * time / runs);
    }

    public static void nothing() {
        // does nothing.
    }
}

打印

A call to nothing took an average of 6.6 ns using reflection
A call to nothing took an average of 10.7 ns using MethodHandle
A call to nothing took an average of 0.4 ns without reflection
A call to nothing took an average of 4.5 ns using reflection
A call to nothing took an average of 9.1 ns using MethodHandle
A call to nothing took an average of 0.0 ns without reflection
A call to nothing took an average of 4.3 ns using reflection
A call to nothing took an average of 8.8 ns using MethodHandle
A call to nothing took an average of 0.0 ns without reflection
A call to nothing took an average of 5.4 ns using reflection
A call to nothing took an average of 13.2 ns using MethodHandle
A call to nothing took an average of 0.0 ns without reflection
A call to nothing took an average of 4.9 ns using reflection
A call to nothing took an average of 8.7 ns using MethodHandle
A call to nothing took an average of 0.0 ns without reflection

我假设MethodHandles比反射更快,但它不会出现。

答案 1 :(得分:0)

我不相信有任何方法可以选择性地禁用JIT优化,除了一些实验性的(如逃逸分析)。

你这样说:

  

我们不想完全关闭它,因为基准测试需要很长时间,而且我们想要对“真正的”运行时进行基准测试。

但是你尝试做的正是如此。在实际运行时,方法调用内联,如果不执行任何操作, 将被优化掉。因此,通过禁止这些优化,您将获得与实际程序中实际发生的不匹配的方法执行时间的测量。

答案 2 :(得分:0)

基准测试的目的是尽可能接近实际性能,所以我不知道你会在这里获得什么。如果您怀疑JIT会做某些事情,并且您不希望在正常使用中实际禁用它,那么您最好的选择是使用该假设来构建基准。如果有一些方法你可以编写基准测试来强调它并使其在JIT下行为效率低下,那么这可能也很有用,因为在分析器下运行基准测试将有助于弄清楚它何时会降低其效率。