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

我从
this answer开始学习了C#中的for和while循环:“只要在条件中使用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编译器,它不限于(int i = 0; i< arr.length; i),它可以在循环外部移动边界检查,例如for(int i = 0; i< 10000000; i),将其减少到一次检查.如果该检查失败,它将运行一个带有完整边界检查的代码版本,以将异常抛出到正确的位置. 对于集合来说,它要复杂得多,因为边界由被调用的方法检查,而不是在调用的地方.通常,它不能从字节码中消除,但JIT和AOT编译器可以消除它,如果它们可以内联方法(这取决于对象的实例化方式和存储位置,除其他外,因为Java中的所有非私有方法是虚拟的,所以编译器需要确保它不需要虚拟调用)但我不知道它们是否真的这样做. C C不检查operator []中的边界.当你使用at时它会检查边界. at是内联的,因此它取决于特定的编译器及其标志,但通常,编译器可以删除边界检查,如果它可以证明无法进行越界访问.它也可以在循环外移动边界检查,但它仍然需要保证异常将被抛出到正确的位置,所以我不知道是否有. 假设在for循环之后不使用int size,C的两个示例在执行死代码消除的编译器中是等效的.如果你使用一些没有内联的方法,后者可能会更快. (你也可以把size的定义放在初始化中:for(int i = 0,size = myvector.size(); i< size; i))

点赞