1

我们正在构建一个用于 Java 字节码程序的平均案例运行时分析的工具。其中一部分是测量实际运行时间。因此,我们将采用任意的、用户提供的方法,该方法可能有也可能没有结果,可能有也可能没有副作用(例如快速排序、阶乘、虚拟嵌套循环……)并执行它(使用反射),测量经过的时间。(我们是否正确地进行基准测试并不重要。)

在基准测试代码中,我们显然不对结果做任何事情(有些方法甚至没有结果)。因此,不知道 JIT 可能会做什么,事实上我们观察到它似乎有时会优化整个基准方法调用。由于基准测试方法在现实中并不是孤立地使用的,这使得基准测试毫无用处。

我们如何防止 JIT 这样做呢?我们不想完全关闭它,因为基准测试需要很长时间,并且无论如何我们都希望对“真实”运行时进行基准测试(因此我们希望 JIT 在方法中处于活动状态

我知道这个问题,但给定的场景太窄了;我们不知道结果类型(如果有的话),因此不能以 JIT 认为没有用的某种方式使用结果。

4

3 回答 3

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 比反射更快,但事实并非如此。

于 2012-08-29T10:48:08.853 回答
0

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

你这样说:

我们不想完全关闭它,因为基准测试需要很长时间,无论如何我们都希望对“真实”运行时进行基准测试。

但你想要做的正是那样。在真正的运行时,方法调用被内联,如果它们不做任何事情,它们将被优化掉。因此,通过抑制这些优化,您将获得与实际程序中实际发生的情况不匹配的方法执行时间测量值。

于 2012-08-29T10:54:31.303 回答
0

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

于 2012-08-29T11:01:27.920 回答