为什么局部可变长度 for 循环更快?分支预测不会减少查找时间的影响吗?

Why are local variable length for-loops faster? Doesn't branch prediction reduce the effect of lookup times?

不久前,我来的时候正在阅读一些 Android performance tips

Foo[] mArray = ...

public void zero() {
    int sum = 0;
    for (int i = 0; i < mArray.length; ++i) {
        sum += mArray[i].mSplat;
    }
}

public void one() {
    int sum = 0;
    Foo[] localArray = mArray;
    int len = localArray.length;

    for (int i = 0; i < len; ++i) {
        sum += localArray[i].mSplat;
    }
}

Google 说:

zero() is slowest, because the JIT can't yet optimize away the cost of getting the array length once for every iteration through the loop.

one() is faster. It pulls everything out into local variables, avoiding the lookups. Only the array length offers a performance benefit.

这完全有道理。但是在对我的计算机体系结构考试想得太多之后,我想起了 Branch Predictors:

a branch predictor is a digital circuit that tries to guess which way a branch (e.g. an if-then-else structure) will go before this is known for sure. The purpose of the branch predictor is to improve the flow in the instruction pipeline.

计算机不是假设 i < mArray.length true 因此,并行计算循环条件和循环体(并且只预测最后一个循环的错误分支),有效地消除任何性能损失?

我也在考虑Speculative Execution:

Speculative execution is an optimization technique where a computer system performs some task that may not be actually needed... The objective is to provide more concurrency...

在这种情况下,计算机将执行代码,既好像循环已经完成又好像它仍在并发进行,再次有效地取消与条件相关的任何计算成本(因为计算机在计算条件时已经在为未来执行计算)?

本质上,我想要了解的事实是,即使 zero() 中的条件比 one() 中的条件计算时间长一点,计算机通常会计算无论如何,在等待检索条件语句的答案时正确的代码分支,因此查找 myAray.length 的性能损失应该无关紧要(反正我就是这么想的)。

这里有什么我没有意识到的吗?


抱歉问题太长了。

提前致谢。

您链接到笔记的站点:

zero() is slowest, because the JIT can't yet optimize away the cost of getting the array length once for every iteration through the loop.

我还没有在 Android 上测试过,但我假设现在是这样。这意味着对于循环的每次迭代,CPU 都必须执行从内存中加载 mArray.length 值的代码。原因是数组的长度可能会改变,因此编译器无法将其视为静态值。

而在 one() 选项中,程序员根据数组长度不会改变的知识显式设置 len 变量。由于这是一个局部变量,编译器可以将它存储在一个寄存器中,而不是在每次循环迭代中从内存中加载它。所以这将减少循环中执行的指令数,并且使分支更容易预测。

你说得对,分支预测有助于减少与循环条件检查相关的开销。但推测的可能性仍然有限,因此在每次循环迭代中执行更多指令会产生额外的开销。此外,许多移动处理器的分支预测器不太先进,不支持那么多的推测。

我的猜测是,在使用像 HotSpot 这样的高级 Java JIT 的现代桌面处理器上,您不会看到 3 倍的性能差异。但我不确定,这可能是一个有趣的实验。