我在我的一个 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()
使用 0
的 recursionDepth
调用时,递归终止。
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.StrangeBehaviour::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/