【问题标题】:Compiler/JIT optimisation of bounds check of for loop in Java and C++Java 和 C++ 中 for 循环边界检查的编译器/JIT 优化
【发布时间】:2016-05-25 02:06:39
【问题描述】:

我从 this answer 在 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 也存在这样的优化吗?

【问题讨论】:

  • 别担心;无论如何,方法调用通常都会被内联。
  • 到目前为止的评论只说内联,但是当arr.length在条件下使用时,不检查边界的优化呢?
  • 我无法解决第一个问题,但对于您的第二个问题(关于 C++),这确实取决于编译器。例如,如果您要对其进行测试,您可能会发现使用带有某些优化设置的 GNU 的 GCC 编译器比使用 Microsoft 的 C++ 编译器可以获得更好的结果。 C++ 是本机/非托管的,因此在编译代码时会应用所有优化。因此,如果不知道将使用哪种编译器和优化设置,就很难判断一种方法的效率。
  • C++ std::vector 根本不检查 operator[] 的边界,因此没有什么可优化的。

标签: java c++ c++11 for-loop compiler-optimization


【解决方案1】:

Java

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

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

C++

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

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

【讨论】:

    猜你喜欢
    • 2013-02-19
    • 2017-04-21
    • 1970-01-01
    • 2011-11-09
    • 2020-10-19
    • 2018-01-04
    • 2012-12-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多