编译器/JIT 优化 Java 和 C++ 中 for 循环的边界检查

Compiler/JIT optimisation of bounds check of for loop in Java and C++

本文关键字:循环 for 边界 检查 C++ JIT 优化 Java 编译器      更新时间:2023-10-16

我从 C# 中forwhile循环的答案中了解到:">只要您在条件中使用arr.Length,编译器/JIT 就会针对此方案进行优化:">

for(int i = 0 ; i < arr.Length ; i++) {
    Console.WriteLine(arr[i]); // skips bounds check
}

这让我怀疑java编译器是否有这样的优化。

for(int i=0; i<arr.length; i++) {
    System.out.println(arr[i]); // is bounds check skipped here?
}

我认为确实如此,嗯,是吗?使用像ArrayList这样的Collection时也会发生同样的情况吗?


但是,如果我必须在 for 循环的主体中使用 myList.size() 的值,考虑到现在myList是一个 ArrayList,该怎么办?所以在这种情况下,吊装myList.size()无济于事,因为size()是方法调用?例如,可能是这样的:

int len = myList.size(); // hoisting for using inside the loop
for(int i = 0; i < myList.size(); i++) { // not using hoisted value for optimization
    System.out.println(myList.get(i));
    if(someOtherVariable == len) {
        doSomethingElse();
    }
}

编辑:虽然我还没有得到java的答案,但我仍然在为这个问题添加第二部分。

问:C++ (C++98/C++11( 是否有此类优化,例如针对vector.size()string.size()?例如,哪个在性能方面更好?

for (int i = 0; i < myvector.size(); ++i)
    cout << myvector[i] << " "; // is bounds checking skipped here, like in C#?

// does this manual optimisation help improve performance, or does it make?
int size = myvector.size();
for (int i = 0; i < size; ++i)
    cout << myvector[i] << " ";

也就是说,这种优化是否也存在于std::string

Java

从 Java 7 开始,编译器消除了对原始数组的边界检查,如果它可以证明无法进行越界访问。在Java 7之前,JIT或AOT编译器可以做到这一点。对于JIT和AOT编译器,它不限于for (int i = 0; i < arr.length; i++),它可以将边界检查移动到循环之外,例如for (int i = 0; i < 10000000; i++),将其减少为单个检查。如果该检查失败,它将运行具有完整边界检查的代码版本,以在正确的位置引发异常。

对于集合,它要复杂得多,因为边界是由调用方法检查的,而不是在调用的位置。通常,它不能从字节码中消除,但如果 JIT 和 AOT 编译器可以内联方法(这取决于对象的实例化方式和存储位置等,因为 Java 中的所有非私有方法都是虚拟的,因此编译器需要确保它不需要虚拟调用(,但我不知道他们是否真的这样做。

C++

C++ 不检查 operator [] 中的边界。当您使用 at 时,它会检查边界。 at 是内联的,因此它取决于特定的编译器及其标志,但通常,编译器可以删除边界检查,如果它可以证明无法进行越界访问。它还可以将边界检查移到循环之外,但它仍然需要保证异常将在正确的位置抛出,所以我不知道是否有任何异常。

C++的两个示例在执行死代码消除的编译器中都是等效的,假设您在for循环后不使用int size。如果您使用某些未内联的方法,后者可能会更快。(你也可以把size的定义放在初始化里面:for (int i = 0, size = myvector.size(); i < size; ++i)

(