我正在阅读一篇文档,其中讨论了Java的just-in-time compiler(JIT)优化技术。其中一个是“循环反转”。文件说:
您使用
while
循环替换常规do-while
循环。而且do-while
循环在if
子句中设置。这种替换可以减少两次跳跃。
循环反转如何工作以及它如何优化我们的代码路径?
NB: 如果有人可以解释一下Java代码的示例以及JIT如何将其优化为本机代码以及为什么它在现代处理器中是最佳的,那将是很棒的。
答案 0 :(得分:107)
while (condition) {
...
}
工作流:
if (condition) do {
...
} while (condition);
工作流:
比较这两个,您可以很容易地看到后者可能根本不会进行任何跳转,前提是循环中只有一步,并且通常跳转次数将比迭代次数少一次。前者必须跳回来检查条件,只有在条件为假时跳出循环。
现代流水线CPU体系结构的跳跃可能非常昂贵:由于CPU在跳转之前完成了检查的执行,超出该跳转的指令已经在管道中间。如果分支预测失败,则必须丢弃所有这些处理。在管道被重新训练时,进一步的执行被延迟。
解释上面提到的分支预测:对于每种条件跳转,CPU都有两条指令,每条指令都包含 bet 的结果。例如,你会在一个循环结束时发出一条说明“如果不为零则跳转,并在零上投注”的指令,因为必须在除最后一次之外的所有迭代上进行跳转。这样,CPU开始使用跳转目标之后的指令而不是跳转指令本身之后的指令来泵送其管道。
请不将此作为如何在源代码级别进行优化的示例。这完全是错误的,因为从你的问题中已经清楚地看到,从第一种形式到第二种形式的转换是JIT编译器作为常规事务完成的事情。
答案 1 :(得分:23)
这可以优化始终至少执行一次的循环。
常规while
循环将始终至少跳回到开头一次并在结束时跳到结尾。运行一次的简单循环示例:
int i = 0;
while (i++ < 1) {
//do something
}
另一方面,do-while
循环将跳过第一次和最后一次跳转。这是上面的一个等效循环,它将在没有跳转的情况下运行:
int i = 0;
if (i++ < 1) {
do {
//do something
} while (i++ < 1);
}
答案 2 :(得分:3)
让我们一起来看看:
while
版本:void foo(int n) {
while (n < 10) {
use(n);
++n;
}
done();
}
n
并跳转到done();
如果条件不正确。n
。done()
。do-while
版本:(请记住,我们实际上并没有在源代码[会引入维护问题]中执行此操作,编译器/ JIT会为我们执行此操作。)
void foo(int n) {
if (n < 10) {
do {
use(n);
++n;
}
while (n < 10);
}
done();
}
n
并跳转到done();
如果条件不正确。n
。done()
。例如,如果n
开始为9
,我们永远不会在do-while
版本中跳转,而在while
版本中,我们必须跳回来开始时,进行测试,然后当我们发现它不正确时跳回到最后。
答案 3 :(得分:3)
循环反转是一种性能优化技术,可以提高性能,因为处理器可以用更少的指令完成相同的结果。这应该主要改善边界条件下的性能。
This link提供了循环反转的另一个例子。在少数几种将递减和比较实现为单个指令集的体系结构中,使用递减和比较操作将for循环转换为while是有意义的。
维基百科有一个很好的例子,我在这里再解释一下。
int i, a[100];
i = 0;
while (i < 100) {
a[i] = 0;
i++;
}
将由编译器转换为
int i, a[100];
i = 0;
if (i < 100) {
do {
a[i] = 0;
i++;
} while (i < 100);
}
这如何转化为效果? 当i的值为99时,处理器不需要执行GOTO(在第一种情况下需要)。这样可以提高性能。