【问题标题】:Can JIT be prevented from optimising away method calls?可以防止 JIT 优化方法调用吗?
【发布时间】:2012-08-23 23:01:40
【问题描述】:

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

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

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

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

【问题讨论】:

  • 遗憾的是,这从未得到真正的答案。 +1 表示“为什么我必须在这么多 cmets 中重新解释我的问题”综合症团结。

标签: java jit compiler-optimization


【解决方案1】:

简单的解决方案是编写一个更现实的基准测试,该基准测试几乎有用,因此不会被优化掉。

有许多技巧可以混淆 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 比反射更快,但事实并非如此。

【讨论】:

  • 即使您知道要进行基准测试的内容,也很难理解结果的含义。我不确定你想要达到什么目的。
  • 我们得到了一些方法(来自用户),并想分别计算给定输入集的运行时间。因此,基准测试代码必须是通用的(对给定方法几乎没有限制)。我们对 JIT 优化 inside 方法很好(事实上,我们希望它这样做),但在某些情况下,JIT 会优化整个方法。从问题中不清楚,我应该编辑吗?
  • 在这种情况下,您的基准测试代码应该告诉用户他们的方法已经被优化到一无所有。其他任何事情都会掩盖 JIT 的真正作用。恕我直言,这正是工具应该告诉用户的事情。
  • 即使您可以可靠地检测到这种优化,这也不是预期的结果。我们不会在实际将执行的地方进行任何基准测试。
  • 您是否使用任何特定的java 设置运行此程序?使用您的示例 Main 类运行 java Main 无法生成您在 Cygwin 上使用 java version "1.7.0_11" 发布的输出。相反,没有反射的情况始终需要 appx 1.4-1.2ns。
【解决方案2】:

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

你这样说:

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

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

【讨论】:

  • 如果我想对将在有用上下文中使用的方法 m 的运行时进行基准测试,那么优化 m 是没有意义的。我说的不是对辅助方法的任何(多余的)调用,而是“主要”方法。
  • 你错过了我的意思。如果“主要”方法将在实际程序中进行优化,那是否意味着不在基准测试中对其进行优化会产生错误的结果?或者你的问题可能没有足够清楚地解释自己......
  • 它只是用于基准测试目的的主要方法(例如,对数组进行排序的方法计算阶乘),而不是在“真实”世界中。
  • 事实仍然存在,如果一个方法没有产生任何结果并且对任何对象没有任何副作用,那么它就不是一段现实的代码,任何使用的基准标记这是可疑的......不管 JIT 编译器如何优化它们。改变方法,使它们是现实的,你不会有这个问题。除此之外,我想不出解决办法。
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 2013-05-31
    • 2018-06-04
    • 1970-01-01
    • 1970-01-01
    • 2013-07-18
    • 1970-01-01
    • 1970-01-01
    • 2017-06-25
    • 2021-05-06
    相关资源
    最近更新 更多