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

5

我从这篇回答中了解到关于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 是否也存在这样的优化呢?


5
别担心,这个方法调用通常会被内联。 - SLaks
3
好的,以下是翻译的结果:问题描述:如何在Java中获取当前时间的毫秒数?解决方案:可以使用System.currentTimeMillis()方法获取当前时间的毫秒数。该方法返回自协调世界时1970年1月1日00:00:00至今所经过的毫秒数。示例代码:long now = System.currentTimeMillis();请注意,这将返回自协调世界时(UTC)1970年1月1日00:00:00以来的毫秒数。 如果您需要本地时间而不是UTC时间,请考虑使用java.util.Datejava.text.SimpleDateFormat类。希望能对你有所帮助。 - SLaks
1
到目前为止,评论只提到了内联的问题,但是在Java中使用arr.length作为条件时不检查边界的优化呢? - Sнаđошƒаӽ
1
我无法回答第一个问题,但对于你的第二个问题(关于C ++),那真的取决于编译器。例如,如果你进行测试,你可能会发现使用GNU的GCC编译器与某些优化设置相比,使用Microsoft的C ++编译器会给你更好的结果。C ++是本地/非托管的,因此所有优化都在代码编译时应用。因此,很难判断一种方法的效率,而不知道将使用哪个编译器和优化设置。 - Spencer D
1
C++中的std::vectoroperator[]中根本不检查边界,因此没有什么可以优化的。 - StenSoft
显示剩余6条评论
1个回答

2

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)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接