注释掉从未执行的代码时,Java 程序运行速度变慢

标签 java recursion compiler-optimization

我在我的一个 Java 程序中观察到一些奇怪的行为。我试图尽可能地剥离代码,同时仍然能够复制行为。完整代码如下。

public class StrangeBehaviour {

    static boolean recursionFlag = true;

    public static void main(String[] args) {
        long startTime = System.nanoTime();
        for (int i = 0; i < 10000; i ++) {
            functionA(6, 0);
        }
        long endTime = System.nanoTime();
        System.out.format("%.2f seconds elapsed.\n", (endTime - startTime) / 1000.0 / 1000 / 1000);
    }

    static boolean functionA(int recursionDepth, int recursionSwitch) {
        if (recursionDepth == 0) { return true; }
        return functionB(recursionDepth, recursionSwitch);
    }

    static boolean functionB(int recursionDepth, int recursionSwitch) {
        for (int i = 0; i < 16; i++) {
            if (StrangeBehaviour.recursionFlag) {
                if (recursionSwitch == 0) {
                    if (functionA(recursionDepth - 1, 1 - recursionSwitch)) return true;
                } else {
                    if (!functionA(recursionDepth - 1, 1 - recursionSwitch)) return false;
                }
            } else {
                // This block is never entered into.
                // Yet commenting out one of the lines below makes the program run slower!
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
                System.out.println("...");
            }
        }
        return false;
    }
}

我有两个函数,functionA()functionB(),它们以递归方式相互调用。这两个函数都带有一个控制递归终止的 recursionDepth 参数。 functionA() 调用 functionB() 最多一次,而 recursionDepth 不变。 functionB() 使用 recursionDepth - 1 调用 functionA() 16 次。当 functionA() 使用 0recursionDepth 调用时,递归终止。

functionB() 有一个代码块,其中包含许多 System.out.println() 调用。这个 block 永远不会进入,因为进入是由一个 boolean recursionFlag 变量控制的,该变量设置为 true 并且在程序执行期间永远不会改变。但是,即使注释掉其中一个 println() 调用也会导致程序运行速度变慢。在我的机器上,所有 println() 调用都存在时,执行时间为 <0.2 秒,当其中一个调用被注释掉时,执行时间为 >2 秒。

什么可能导致这种行为?我唯一的猜测是,与代码块长度(或函数调用次数等)相关的参数触发了一些天真的编译器优化。对此的任何进一步见解将不胜感激!

编辑:我使用的是 JDK 1.8。

最佳答案

注释代码会影响内联的处理方式。 如果 functionB 变得更长/更大(更多字节码指令),它将不会内联到 functionA。

So @J3D1 was able to use VMOptions to manually switch off inlining for functionB(): -XX:CompileCommand=dontinline,com.jd.benchmarking.StrangeBeh‌​aviour::functionB This appears to eliminate the delay with the shorter function.

使用 vm 选项可以显示内联 -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining

更大的版本,不会内联函数B

@ 8   StrangeBehaviour::functionB (326 bytes)   callee is too large
@ 21   StrangeBehaviour::functionA (12 bytes)
  @ 8   StrangeBehaviour::functionB (326 bytes)   callee is too large
@ 35   StrangeBehaviour::functionA (12 bytes)
  @ 8   StrangeBehaviour::functionB (326 bytes)   callee is too large

较短的版本会尝试内联函数 B,导致进一步尝试。

@ 8   StrangeBehaviour::functionB (318 bytes)   inline (hot)
 @ 21   StrangeBehaviour::functionA (12 bytes)   inline (hot)
   @ 8   StrangeBehaviour::functionB (318 bytes)   inline (hot)
     @ 35   StrangeBehaviour::functionA (12 bytes)   recursive inlining is too deep
 @ 35   StrangeBehaviour::functionA (12 bytes)   inline (hot)
   @ 8   StrangeBehaviour::functionB (318 bytes)   inline (hot)
     @ 21   StrangeBehaviour::functionA (12 bytes)   recursive inlining is too deep
     @ 35   StrangeBehaviour::functionA (12 bytes)   recursive inlining is too deep
@ 21   StrangeBehaviour::functionA (12 bytes)   inline (hot)
 @ 8   StrangeBehaviour::functionB (318 bytes)   inline (hot)
   @ 35   StrangeBehaviour::functionA (12 bytes)   inline (hot)
     @ 8   StrangeBehaviour::functionB (318 bytes)   recursive inlining is too deep
@ 35   StrangeBehaviour::functionA (12 bytes)   inline (hot)
 @ 8   StrangeBehaviour::functionB (318 bytes)   inline (hot)
   @ 21   StrangeBehaviour::functionA (12 bytes)   inline (hot)
    @ 8   StrangeBehaviour::functionB (318 bytes)   recursive inlining is too deep
   @ 35   StrangeBehaviour::functionA (12 bytes)   inline (hot)
     @ 8   StrangeBehaviour::functionB (318 bytes)   recursive inlining is too deep

主要是猜测,但更大/内联的字节码会导致分支预测和缓存出现问题

关于注释掉从未执行的代码时,Java 程序运行速度变慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41410743/

相关文章:

相当于 OpenSSL AES CBC 加密的 Java

java - JPQL:SELECT NEW 查询中的枚举文字

Python:如何递归地从嵌套数据结构(列表和字典)中删除无值?

arrays - Clojure中Heap的算法(能否高效实现?)

compiler-construction - 位置独立代码和共享对象

rust - 调用函数时移动所有权是否会复制 `self` 结构?

clang - 使用 clang 优化 channel /标志进行编译

java - 如何使用collapsing toolbar布局实现这种布局效果

java - 在 web 元素中查找动态样式属性

windows - 递归重命名子文件夹中的文件 Windows 批处理文件