为什么Java的JIT编译器似乎没有删除无用的算术运算(+0; *1)?

Nic*_*ini 5 java performance jit

我试图了解当代码被执行多次时,我可以在 Java 中实现什么样的编译时优化。我对以下场景中的算术简化特别感兴趣:

想象一下,您需要使用相同的 3d 仿射变换来变换一百万个 3d 点。如果转换结果是纯翻译,那么一个好的优化器将能够将 12 次乘法和 12 次加法仅转换为 3 次加法,因为所有乘法都是乘以 1,而许多加法都是带零的加法。

在尝试这个“复杂场景”之前,我只是在循环中运行了一些乘法和加法,尽管我一直在阅读有关 Java 的 JIT 编译器的很酷的内容,但我还是有点失望。

首先 - 什么有效:执行许多乘以 1 似乎被简化并且执行得非常快:

        tic();
        final int nRepetitions = 100_000_000;
        final double factor1 = 1.0d;
        value = 0.0d;
        for (int i=0;i<nRepetitions;i++) {
            for (int j=0;j<20;j++) {
                value = value * factor1;
            }
        }
        System.out.println("Result = "+value);
        toc();
Run Code Online (Sandbox Code Playgroud)

我得到这个执行速度,很快:

 Result with graalvm-ce-17\bin\java.exe
 ----------------------
 Repeating 100000000 multiplication by factor1 = 1.0, a final variable
 The code is put in the main method
 This is overall is No op, and should be super fast
 Result = 0.0
 Elapsed time   64.8528  ms
 ----------------------
Run Code Online (Sandbox Code Playgroud)

如果我执行相同的计算,但通过调用函数,我根本没有得到任何优化,执行速度约为 2 秒。

 Result with graalvm-ce-17\bin\java.exe
 ----------------------
 Repeating 100000000 multiplication by factor1 = 1.0, a final variable
 The code is put in the main method
 This is overall is No op, and should be super fast
 Result = 0.0
 Elapsed time   64.8528  ms
 ----------------------
Run Code Online (Sandbox Code Playgroud)

其功能为:

    public static void repeatMultiply(int nRepetitions, double value, final double multFactor) {
        for (int i=0;i<nRepetitions;i++) {
            for (int j=0;j<20;j++) {
                value = value * multFactor;
            }
        }
        System.out.println("Result = "+value);
    }
Run Code Online (Sandbox Code Playgroud)

现在代码运行速度非常慢:

 ----------------------
  Repeating 100000000 multiplication by factor1 = 1.0d
  Function called = repeatMultiply
  This is overall is No op, and should be super fast
  Result = 0.0
  Elapsed time  1815.1354    ms
Run Code Online (Sandbox Code Playgroud)

我测试过其他东西。factor1不像第一个示例中那样声明变量final会破坏我看到的唯一优化。然后,我尝试加零而不是乘以一,但情况更糟:我总是得到“长”执行时间,在我的机器上大约为两秒。

我测试过 Oracle JDK v1.8 和 v18,还有 Graal VM Community Edition 17,但似乎没有什么区别。我已经在https://gist.github.com/NicoKiaru/2949e6969087e75b07b21596d80c7882中执行了所有代码和测试的要点

我希望您能启发我,让我知道这些结果是否反映了 Java JIT 编译器的内在限制,即我在测试中是否做错了什么。

有什么方法可以通过 JIT 编译来实现我的目标(仿射变换计算的自动优化),或者我应该停止梦想并在 Java 代码中显式测试“简单场景”(如翻译)?

编辑

计算常量 JEP https://openjdk.org/jeps/8312611是否有可能允许此类优化,或者完全不相关?

小智 0

JIT 编译器对于进行可能改变代码行为的优化持谨慎态度。尽管从数学角度来看,加零或乘以一似乎是不必要的,但这些操作可能出于特定原因(例如代码可读性或处理边缘情况)而明确包含在代码中。删除它们可能会改变程序的语义。