Java - 重复的函数调用减少了执行时间

Java - Repeated function call reduces execution time

我有以下代码

public class BenchMark {
    public static void main(String args[]) {
        doLinear();

        doLinear();

        doLinear();

        doLinear();

    }


    private static void doParallel() {
        IntStream range = IntStream.range(1, 6).parallel();

        long startTime = System.nanoTime();
        int reduce = range
                .reduce((a, item) -> a * item).getAsInt();
        long endTime = System.nanoTime();
        System.out.println("parallel: " +reduce + " -- Time: " + (endTime - startTime));
    }

    private static void doLinear() {
        IntStream range = IntStream.range(1, 6);

        long startTime = System.nanoTime();
        int reduce = range
                .reduce((a, item) -> a * item).getAsInt();
        long endTime = System.nanoTime();
        System.out.println("linear: " +reduce + " -- Time: " + (endTime - startTime));
    }

}

我试图对流进行基准测试,但在一次又一次调用相同函数后,执行时间稳步减少

输出:

linear: 120 -- Time: 57008226
linear: 120 -- Time: 23202
linear: 120 -- Time: 17192
linear: 120 -- Time: 17802

Process finished with exit code 0

第一次和第二次执行时间存在巨大差异

我确定 JVM 可能在幕后做了一些小把戏,但有人可以帮助我了解那里到底发生了什么吗?

有没有办法避免这种优化,以便我可以对真实执行时间进行基准测试?

更新:请参阅@Marko 的回答,了解因 lambda 链接引起的初始延迟的解释。


第一次调用的执行时间较长可能是 JIT effect 的结果。简而言之,将字节代码 JIT 编译为本机机器代码发生在第一次调用您的方法时。 JVM 然后通过识别经常调用的(热)方法尝试进一步优化,并重新生成它们的代码以获得更高的性能。

Is there anyway to avoid this optimization so I can benchmark true execution time ?

您当然可以通过排除前几个结果来解释 JVM 初始预热。然后在几万次迭代的循环中增加对你的方法的重复调用次数,取平均值。

您可能还想考虑将更多选项添加到您的执行中,以帮助减少本 post. There are also some good tips from this post 中讨论的噪音。

Java VM 在第一次使用 class 时将 class 加载到内存中。 所以第一个和第二个运行之间的差异可能是由class加载引起的。

I'm sure JVM might be doing some tricks behind the scenes but can anybody help me understand whats really going on there?

  1. 第一次调用的大量延迟是由于完整的 lambda 运行时子系统的初始化造成的。您只需为整个申请支付一次费用。

  2. 您的代码第一次到达任何给定的 lambda 表达式时,您需要为该 lambda 的 链接 付费(invokedynamic 调用站点的初始化).

  3. 经过一些迭代后,由于 JIT 编译器优化了您的缩减代码,您将看到额外的加速。

Is there anyway to avoid this optimization so I can benchmark true execution time?

您在这里提出了一个矛盾:"true" 执行时间是您在 预热后获得的执行时间,当所有优化都已应用时。这是实际应用程序将经历的运行时。前几次运行的延迟与更广泛的图片无关,除非您对单次性能感兴趣。

为了探索,您可以查看代码在禁用 JIT 编译的情况下的行为:将 -Xint 传递给 java 命令。还有更多的标志禁用优化的各个方面。

true execution time

"true execution time" 绝无仅有。如果您只需要解决此任务一次,那么真正的执行时间将是第一次测试的时间(以及启动 JVM 本身的时间)。一般来说,执行给定代码所花费的时间取决于很多因素:

  • 这段代码是否解释,由C1或C2编译器JIT编译。请注意,不仅有三个选项。如果您从另一个调用一个方法,其中一个可能会被解释,而另一个可能会被 C2 编译。

  • 对于 C2 编译器:这段代码之前是如何执行的,那么分支和类型配置文件中有什么。受污染的类型配置文件会大大降低性能。

  • 垃圾收集器状态:是否中断执行

  • 编译队列:JIT-compiler是否同时编译其他代码(可能会减慢当前代码的执行速度)

  • 内存布局:对象在内存中的位置,应加载多少缓存行以访问所有必要数据。

  • CPU 分支预测器状态取决于之前的代码执行,可能会增加或减少分支错误预测的数量。

等等等等。因此,即使您在孤立的基准测试中测量某些东西,这并不意味着相同代码在生产中的速度会相同。它可能在数量级上有所不同。所以在测量某样东西之前你应该问问自己为什么要测量这个东西。通常您不关心程序的某些部分执行了多长时间。你通常关心的是整个程序的延迟和吞吐量。因此,分析整个程序并优化最慢的部分。可能您正在测量的东西不是最慢的。