Compiler/JIT Java 和 C++ 中 for 循环边界检查的优化
Compiler/JIT optimisation of bounds check of for loop in Java and C++
我从 this answer 的 for
和 while
C# 循环中了解到:"The compiler/JIT has optimisations for this scenario as long as you use arr.Length
in the condition:"
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 怎么办?所以在那种情况下 hoisting 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 的答案,但我仍在为这个问题添加第二部分。
Q: C++ (C++98/C++11) 是否有这样的优化,例如vector.size()
and 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 起,如果编译器可以证明越界访问是不可能的,则编译器将消除对原始数组的边界检查。在 Java7 之前,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
是内联的,因此它取决于特定的编译器及其标志,但通常情况下,编译器可以删除边界检查,如果它可以证明越界访问是不可能的。它还可以将边界检查移到循环之外,但它仍然需要保证异常将在正确的位置抛出,所以我不知道是否有。
假设您在 for
循环后不使用 int size
,则 C++ 的两个示例在执行死代码消除的编译器中将是等效的。如果您使用一些未内联的方法,后者可能会更快。 (也可以把size
的定义放在初始化里面:for (int i = 0, size = myvector.size(); i < size; ++i)
)
我从 this answer 的 for
和 while
C# 循环中了解到:"The compiler/JIT has optimisations for this scenario as long as you use arr.Length
in the condition:"
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 怎么办?所以在那种情况下 hoisting 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 的答案,但我仍在为这个问题添加第二部分。
Q: C++ (C++98/C++11) 是否有这样的优化,例如vector.size()
and 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 起,如果编译器可以证明越界访问是不可能的,则编译器将消除对原始数组的边界检查。在 Java7 之前,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
是内联的,因此它取决于特定的编译器及其标志,但通常情况下,编译器可以删除边界检查,如果它可以证明越界访问是不可能的。它还可以将边界检查移到循环之外,但它仍然需要保证异常将在正确的位置抛出,所以我不知道是否有。
假设您在 for
循环后不使用 int size
,则 C++ 的两个示例在执行死代码消除的编译器中将是等效的。如果您使用一些未内联的方法,后者可能会更快。 (也可以把size
的定义放在初始化里面:for (int i = 0, size = myvector.size(); i < size; ++i)
)